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

Spl*_*iFF 9 sql postgresql plpgsql

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

想象一下以下2个表.

T1
id | title | extra
1    A     | value

- and -

T2
id | title
1    A
Run Code Online (Sandbox Code Playgroud)

我希望能够使用相同的查询从这些表中的任何一个中进行选择.

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

 SELECT id,title, COALESCE(extra, 'default') as extra
Run Code Online (Sandbox Code Playgroud)

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

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

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

Erw*_*ter 14

为什么罗文的黑客工作(大部分)?

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
Run Code Online (Sandbox Code Playgroud)

通常,它根本不起作用.如果任何涉及的列不存在,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
Run Code Online (Sandbox Code Playgroud)

快点

由于此查询几乎无法移植到其他RDBMS,我建议使用目录表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;
Run Code Online (Sandbox Code Playgroud)

还使用更方便和安全的演员regclass - 这里详细解释:
regclass在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;
Run Code Online (Sandbox Code Playgroud)

方便

您可以在一个简单的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!)';
Run Code Online (Sandbox Code Playgroud)

将查询简化为:

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);
Run Code Online (Sandbox Code Playgroud)

在这里使用具有附加关系的表单,因为事实证明该功能更快.

但是,您只能使用任何这些查询获取列的文本表示.获得实际类型并不简单.

基准

我在pg 9.1和9.2上运行了一个带有10万行的快速基准测试,以发现这些最快:

-- 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);
Run Code Online (Sandbox Code Playgroud)

- > SQLfiddle演示.


Row*_*wan 6

一种方法是查找信息模式表并用它做一些魔术。

就像是:

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
Run Code Online (Sandbox Code Playgroud)

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