我有一个查询,如果缺少某个列,我基本上需要一个回退值。我想知道我是否可以在我的查询中完全处理这个问题(而不是先探测并发送一个单独的查询。本质上我正在寻找一个等同于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中而不是在应用程序逻辑中这样做(或者为什么我不会只是将列永久地添加到模式中)所以请限制您对特定请求的评论/答案,而不是您对数据库“正确性”的看法或其他可能冒犯您对此问题的看法。
答案 0 :(得分:13)
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);
答案 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'。