当列不存在时,Postgres返回默认值

时间:2013-09-23 02:45:10

标签: sql postgresql plpgsql

我有一个查询,如果缺少某个列,我基本上需要一个回退值。我想知道我是否可以在我的查询中完全处理这个问题(而不是先探测并发送一个单独的查询。本质上我正在寻找一个等同于COALESCE来处理丢失列的情况。

想象一下以下两个表格。

T1
id | title | extra
1    A     | value

- and -

T2
id | title
1    A

我希望能够使用相同的查询从这些表格中选择。

例如,如果t2实际上有一个“额外”列,我可以使用

 SELECT id,title, COALESCE(extra, 'default') as extra

但是只有在列值为NULL时才有效,而不是在列完全丢失时。

我更喜欢SQL版本,但我也可以接受PLPGSQL函数(行为与COALLESCE类似)。

对SQL纯粹主义者的注意:我真的不想辩论为什么我想在SQL中而不是在应用程序逻辑中这样做(或者为什么我不会只是将列永久地添加到模式中)所以请限制您对特定请求的评论/答案,而不是您对数据库“正确性”的看法或其他可能冒犯您对此问题的看法。

2 个答案:

答案 0 :(得分:13)

Rowan's hack为什么(大多数情况下)工作?

SELECT id, title, CASE WHEN extra_exists THEN extra::text
                                         ELSE 'default'::text END AS extra
FROM   mytable
CROSS JOIN (
   SELECT EXISTS (
      SELECT 1 
      FROM   information_schema.columns 
      WHERE  table_name = 'mytable'
      AND    column_name = 'extra') AS extra_exists
   ) AS extra

通常,它根本不起作用。如果任何所涉及的列不存在,Postgres会解析SQL语句并抛出异常。

诀窍是引入一个与所讨论的列名同名的表名(或别名)。在这种情况下extra。每个表名都可以作为一个整体引用,这会导致整行返回类型record。由于每种类型都可以转换为text,我们可以将整个记录转换为text。这样,Postgres接受查询为有效。

由于列名优先于表名,因此如果列存在,extra::text将被解释为列mytable.extra。否则,它将默认返回表extra的整行 - 这从未发生过。

尝试为extra选择一个不同的表别名,以便自己查看。

如果Postgres决定改变未来版本中计划的SQL字符串解析方式,那么这是一个未记录的黑客并且可能会破坏 - 即使这似乎不太可能。

无歧义的

如果您决定使用此功能,至少明确无误

仅表格名称不是唯一的。名为“mytable”的表可以在同一数据库的多个模式中存在任意次数,这可能导致非常混乱和完全错误的结果。您需要另外提供架构名称:

SELECT id, title, CASE WHEN col_exists THEN extra::text
                                       ELSE 'default'::text END AS extra
FROM   mytable
CROSS JOIN (
   SELECT EXISTS (
      SELECT 1 
      FROM   information_schema.columns 
      WHERE  table_schema = 'public'
      AND    table_name = 'mytable'
      AND    column_name = 'extra'
      ) AS col_exists
   ) extra

更快

由于此查询几乎无法移植到其他RDBMS,我建议使用catalog table pg_attribute而不是信息架构视图information_schema.columns。大约快10倍。

SELECT id, title, CASE WHEN col_exists THEN extra::text
                                       ELSE 'default'::text END AS extra
FROM   mytable
CROSS JOIN (
   SELECT EXISTS (
      SELECT 1 
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = 'myschema.mytable'::regclass  -- schema-qualified!
      AND    attname  = 'extra'
      AND    NOT attisdropped    -- no dropped (dead) columns
      AND    attnum   > 0        -- no system columns
      ) AS col_exists
   ) extra;

同时使用更方便,更安全的演员regclass - 详细说明如下:
What does regclass signify in Postgresql

您可以将所需的别名附加到傻瓜Postgres到任何表,包括主表本身。您根本不需要加入另一个关系,这应该是最快的:

SELECT id, title, CASE WHEN EXISTS (
         SELECT 1 
         FROM   pg_catalog.pg_attribute
         WHERE  attrelid = 'mytable'::regclass
         AND    attname  = 'extra'
         AND    NOT attisdropped
         AND    attnum   > 0
         ) THEN extra::text ELSE 'default'::text END AS extra
FROM mytable AS extra;

便利

你可以将测试存在于一个简单的SQL函数中(一次),几乎到达你一直要求的函数:

CREATE OR REPLACE FUNCTION col_exists(_tbl regclass, _col text)
  RETURNS bool AS
$func$
SELECT EXISTS (
   SELECT 1
   FROM   pg_catalog.pg_attribute
   WHERE  attrelid = $1
   AND    attname  = $2
   AND    NOT attisdropped
   AND    attnum   > 0
   )
$func$
  LANGUAGE sql STABLE;

COMMENT ON FUNCTION col_exists(regclass, text) IS
'Test for existence of a column. Returns TRUE / FALSE.
$1 .. exact table name (case sensitive!), optionally schema-qualified
$2 .. exact column name (case sensitive!)';

将查询简化为:

SELECT id, title, CASE WHEN col_exists THEN extra::text
                                       ELSE 'default'::text END AS extra
FROM   mytable
CROSS  JOIN col_exists('mytable', 'extra') AS extra(col_exists);

在这里使用具有附加关系的表格,因为事实证明它更快。

但是,您只能使用以下任何查询获取列的文本表示。获得实际类型并不是那么简单。

基准

我在pg 9.1和9.2上运行了一个100k行的快速基准测试,发现这些最快:

-- fastest
SELECT id, title, CASE WHEN EXISTS (
          SELECT 1 
          FROM   pg_catalog.pg_attribute
          WHERE  attrelid = 'mytable'::regclass
          AND    attname  = 'extra'
          AND    NOT attisdropped
          AND    attnum   > 0
          ) THEN extra::text ELSE 'default'::text END AS extra
FROM   mytable AS extra;

-- 2nd fastest
SELECT id, title, CASE WHEN col_exists THEN extra::text
                                       ELSE 'default'::text END AS extra
FROM   mytable
CROSS  JOIN col_exists('mytable', 'extra') AS extra(col_exists);

-> SQLfiddle demo.

答案 1 :(得分:4)

一种方法是查找信息架构表,并用它做一点魔力。

类似的东西:

SELECT id, title, CASE WHEN extra_exists THEN extra ELSE 'default' END AS extra
FROM mytable
CROSS JOIN (
SELECT EXISTS (SELECT 1 
FROM information_schema.columns 
WHERE table_name='mytable' AND column_name='extra') AS extra_exists) extra

编辑:需要为要查询的表传入'mytable'。