SELECT id, title
, CASE WHEN extra_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_name = 'tbl'
AND column_name = 'extra')
) AS extra(extra_exists)
通常,它根本不起作用。Postgres 解析 SQL 语句并在任何涉及的列不存在时抛出异常。
诀窍是引入与相关列名同名的表名(或别名)。extra
在这种情况下。每个表名都可以作为一个整体引用,这导致整行返回为 type record
。而且由于每种类型都可以转换为text
,我们可以将整个记录转换为text
. 这样,Postgres 将查询视为有效。
由于列名优先于表名,因此如果该列存在,则将其extra::text
解释为该列。tbl.extra
否则,它将默认返回表的整行extra
——这永远不会发生。
尝试选择不同的表别名extra
以供您自己查看。
这是一个未记录的 hack,如果 Postgres 决定在未来的版本中改变 SQL 字符串的解析和计划方式,它可能会中断——即使不太可能。
明确的
如果您决定使用它,至少要明确。
单独的表名不是唯一的。一个名为“tbl”的表可以在同一个数据库的多个模式中存在任意多次,这可能会导致非常混乱和完全错误的结果。您需要另外提供架构名称:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'tbl'
AND column_name = 'extra'
) AS col_exists
) extra;
快点
由于这个查询很难移植到其他 RDBMS,我建议使用目录表pg_attribute
而不是信息模式视图information_schema.columns
。大约快 10 倍。
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'myschema.tbl'::regclass -- schema-qualified!
AND attname = 'extra'
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
)
) extra(col_exists);
还使用了更方便、更安全的cast toregclass
。看:
您可以附加所需的别名来欺骗 Postgres 到任何表,包括主表本身。您根本不需要加入另一个关系,这应该是最快的:
SELECT id, title
, CASE WHEN EXISTS (SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0)
THEN extra::text
ELSE 'default' END AS extra
FROM tbl AS extra;
方便
您可以将测试是否存在封装在一个简单的 SQL 函数中(一次),(几乎)到达您一直要求的函数:
CREATE OR REPLACE FUNCTION col_exists(_tbl regclass, _col text)
RETURNS bool
LANGUAGE sql STABLE AS
$func$
SELECT EXISTS (
SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = $1
AND attname = $2
AND NOT attisdropped
AND attnum > 0
)
$func$;
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' END AS extra
FROM tbl
CROSS JOIN col_exists('tbl', 'extra') AS extra(col_exists);
在此处使用具有附加关系的表单,因为使用该功能会更快。
不过,您只能通过任何这些查询获得该列的文本表示形式。获取实际类型并不是那么简单。
基准
我在 pg 9.1 和 9.2 上运行了一个包含 100k 行的快速基准测试,以发现这些是最快的:
最快的:
SELECT id, title
, CASE WHEN EXISTS (SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0)
THEN extra::text
ELSE 'default' END AS extra
FROM tbl AS extra;
第二快:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN col_exists('tbl', 'extra') AS extra(col_exists);
db<>fiddle here
旧sqlfiddle