我们假设你有一张表格的Postgres表:
f1 | f2 | metadata
-----+-------+-----------------------
a | 33 | {"f3": "d", "f4": "e"}
b | 20 | {"f3": "d", "f4": "g"}
metadata
列是非结构化JSON字段。如何查询此表,以便结果记录包含字段f1
,f2
,f3
和f4
,并扩展JSON以填充这些字段?
我知道json_populate_record()
如果您事先知道f3
和f4
是字段名称,则可以执行此操作。但我不是。我想使用第一行metadata
中的键名作为所有其他行的模板。
换句话说:我希望查询的结果列为f1
,f2
+ 第一行的JSON数据的所有键。任何其他不符合第一行中的键的键都应该被删除。
答案 0 :(得分:2)
没有自然的“第一”排。你需要定义“第一”。
假设ORDER BY f1, f2
的第一行。
如果您不知道预期列的数量和数据类型,则 无法在单个SQL语句中完成。 SQL要求知道返回类型,至少在调用时。但是有两种方法可以用两种方式来做。
这是一个适合的测试设置:
CREATE TABLE tbl (f1 text, f2 int, metadata jsonb);
INSERT INTO tbl VALUES
('a', 33, '{"f3": "d", "f4": "e"}') -- "first" row
, ('b', 20, '{"f3": "d", "f4": "g"}') -- same keys
, ('c', 40, '{"f7": "x", "f4": "o"}') -- one matching key
, ('d', 50, '{"f3": "o", "f9": "x", "f123": "z"}') -- one match, two miss
, ('e', 60, '{"x": "d", "y": "g"}') -- no match
, ('f', 33, '{"f3": 1, "f4": false}'); -- numeric and boolean
知道 数字 , 名称 和 类型 的结果列,它变得简单,就像你在问题中提到的那样。我建议你创建一个临时表来提供jsonb_populate_record()
的行类型:
BEGIN;
CREATE TEMP TABLE tmp(f3 text, f4 text) ON COMMIT DROP;
SELECT f1, f2, meta.*
FROM tbl, jsonb_populate_record(NULL::tmp, metadata) meta;
ROLLBACK; -- or: COMMIT;
ON COMMIT DROP
会自动删除事务结束时的表。你可能想要也可能不想要那样。如果这样做,请对两个命令使用单个事务。
临时表仅在同一会话中可见,因此与多个执行相同操作的事务没有命名冲突。
如果您没有这些信息,那就更复杂了。
您可以使用DO
命令和动态SQL执行相同的操作:
DO
$do$
BEGIN
EXECUTE 'CREATE TEMP TABLE tmp(f3 text, f4 text) ON COMMIT DROP';
END
$do$;
由于我们实际上并不知道输出列的数量和名称,因此我们从第一行中提取该信息。假设:
text
。jsonb
列中至少有一个键。
DO
$do$
BEGIN
EXECUTE (
SELECT (SELECT 'CREATE TEMP TABLE tmp('
|| string_agg(quote_ident(k), ' text, ') -- f3 text, f4
|| ' text) ON COMMIT DROP'
FROM jsonb_object_keys(metadata) k)
FROM tbl
ORDER BY f1, f2
LIMIT 1
);
END
$do$;
SELECT f1, f2, meta.*
FROM tbl, jsonb_populate_record(NULL::tmp, metadata) meta;
VOILÀ。
请务必使用quote_ident()
或类似内容正确转义密钥名称。
如果事先知道列名......
(addressing your comment),你可以简单地说:
SELECT f1, f2, metadata->>'f3', metadata->>'f4'
FROM tbl;
对于宽行,jsonb_populate_record()
更方便。您仍然可以使用动态解决方案,或者保留表或类型并使用它。
如果您的第二个命令可以 依赖 ,那么您也可以动态构建简单语句并在另一个调用中执行该命令:
SELECT (SELECT 'SELECT f1, f2, metadata->>'
|| string_agg(format('%1$L AS %1$I', k), ', metadata->>')
|| ' FROM tbl'
FROM jsonb_object_keys(metadata) k)
FROM tbl
ORDER BY f1, f2
LIMIT 1;
以文本形式返回上述简单查询。执行它作为第二个命令...执行可能会快一点,但它需要两个往返服务器,而第一个解决方案可以只用一个...你决定。
在此处使用format()
来简化安全查询字符串连接。