在 Postgres 中收集递归 JSON 密钥
Posted
技术标签:
【中文标题】在 Postgres 中收集递归 JSON 密钥【英文标题】:Collect Recursive JSON Keys In Postgres 【发布时间】:2015-07-19 21:15:05 【问题描述】:我有 JSON 文档以 JSON 数据类型 (Postgres 9.3) 存储在 Postgres 中,我需要递归地收集树中的键名。
例如,给定这个 JSON 树
"files":
"folder":
"file1":
"property": "blah"
,
"file2":
"property": "blah"
,
"file3":
"property": "blah"
,
"file4":
"property": "blah"
,
"software":
"apt":
"package1":
"version": 1.2
,
"package2":
"version": 1.2
,
"package3":
"version": 1.2
,
"package4":
"version": 1.2
我想提取 [file1,file2,file3,file3,package1,package2,package3,package4] 之类的内容
基本上只是我可以用于文本搜索索引的键列表。
我知道我可以使用类似的方法获取最外层对象的键列表
SELECT DISTINCT(json_object_keys(data))
而且我知道可以使用类似
的方法递归地爬过树WITH RECURSIVE data()
但我无法将两者放在一起。
谁能帮忙?
【问题讨论】:
【参考方案1】:诀窍是在正确的位置使用json_typeof
添加一些最终条件测试。
如果您不关心对象键顺序,也应该使用jsonb
。
这是我的工作环境:
CREATE TABLE test (
id SERIAL PRIMARY KEY,
doc JSON
);
INSERT INTO test (doc) VALUES ('
"files":
"folder":
"file1":
"property": "blah"
,
"file2":
"property": "blah"
,
"file3":
"property": "blah"
,
"file4":
"property": "blah",
"prop" :
"clap": "clap"
,
"software":
"apt":
"package1":
"version": 1.2
,
"package2":
"version": 1.2
,
"package3":
"version": 1.2
,
"package4":
"version": 1.2
');
当第二个查询没有返回任何行时,递归停止。这是通过将一个空对象传递给json_each
来完成的。
WITH RECURSIVE doc_key_and_value_recursive(key, value) AS (
SELECT
t.key,
t.value
FROM test, json_each(test.doc) AS t
UNION ALL
SELECT
t.key,
t.value
FROM doc_key_and_value_recursive,
json_each(CASE
WHEN json_typeof(doc_key_and_value_recursive.value) <> 'object' THEN '' :: JSON
ELSE doc_key_and_value_recursive.value
END) AS t
)
SELECT *
FROM doc_key_and_value_recursive
WHERE json_typeof(doc_key_and_value_recursive.value) <> 'object';
【讨论】:
哦,伙计.. 你刚刚救了我几个小时的挫败感。我整个下午都在用头撞桌子,试图让它工作,你把它钉在了一起。该查询还允许我通过对 where 子句的简单更改来提取属性或键值。非常感谢! 这个解决方案是正确的。感谢那!关于如何迭代数组中对象中的键的任何建议?如"stuff": ["thing": "a", "thing": "b"]
你应该可以加入一些json_array_elements()
:)
知道了。我更改了第二个 SELECT t.key, t.value
以包含一个案例声明:SELECT t.key, CASE WHEN json_typeof(t.value) = 'array' THEN json_array_elements(t.value) ELSE t.value END AS value ...
假设我没有任何数组混合它,我可以使用这个导出密钥的路径吗?即结果是一个点分隔的关键路径,例如parent.child.grandchild【参考方案2】:
一个更简洁的版本,您可以使用它进行测试:
WITH RECURSIVE reports (key, value) AS (
SELECT
NULL as key,
'"k1": "k2": "v1", "k3": "k4": "v2", "k5": "v3"'::JSONB as value
UNION ALL
SELECT
jsonb_object_keys(value)as key,
value->jsonb_object_keys(value) as value
FROM
reports
WHERE
jsonb_typeof(value) = 'object'
)
SELECT
*
FROM
reports;
这将返回一个列表,然后您需要将其与 distinct 分组。
【讨论】:
Simon 上面的函数不太通用:'"A":[[["C":"B"]]],"X":"Y"'::JSONB
的结果不同,因为数组中有对象。【参考方案3】:
我为此编写了一个函数:
CREATE OR REPLACE FUNCTION public.jsonb_keys_recursive(_value jsonb)
RETURNS TABLE(key text)
LANGUAGE sql
AS $function$
WITH RECURSIVE _tree (key, value) AS (
SELECT
NULL AS key,
_value AS value
UNION ALL
(WITH typed_values AS (SELECT jsonb_typeof(value) as typeof, value FROM _tree)
SELECT v.*
FROM typed_values, LATERAL jsonb_each(value) v
WHERE typeof = 'object'
UNION ALL
SELECT NULL, element
FROM typed_values, LATERAL jsonb_array_elements(value) element
WHERE typeof = 'array'
)
)
SELECT DISTINCT key
FROM _tree
WHERE key IS NOT NULL
$function$;
举个例子,试试:
SELECT jsonb_keys_recursive('"A":[[["C":"B"]]],"X":"Y"');
请注意,其他两个答案在数组内的对象中找不到键,我的解决方案可以。 (这个问题根本没有给出任何数组的例子,所以在数组中查找键可能不是最初的提问者需要的,但这是我需要的。)
【讨论】:
【参考方案4】:@Simon 上面的回答很棒,但是对于我构建 JSON 对象差异的类似情况,我希望有 JSONpath 形式的键路径,不仅是姓氏,还包括数组索引和值。
所以,例如"A":[[["C":"B", "D":"E"]]],"X":"Y", "F": "G": "H"
,我不仅需要键X
、D
、G
、C
、F
、A
,还需要每个路径上的值,例如.A[0][0][0].C
= ' B'。
还有一些小的改进,例如:
-
提供值的数据类型
自行提供价值,无需额外引号
我希望它对某人也有帮助:
WITH RECURSIVE _tree (key, value, type) AS (
SELECT
NULL as key
,'"A":[[["C":"B", "D":"E"]]],"X":"Y", "F": "G": "H"'::jsonb as value
,'object'
UNION ALL
(
WITH typed_values AS (
SELECT key, jsonb_typeof(value) as typeof, value FROM _tree
)
SELECT CONCAT(tv.key, '.', v.key), v.value, jsonb_typeof(v.value)
FROM typed_values as tv, LATERAL jsonb_each(value) v
WHERE typeof = 'object'
UNION ALL
SELECT CONCAT(tv.key, '[', n-1, ']'), element.val, jsonb_typeof(element.val)
FROM typed_values as tv, LATERAL jsonb_array_elements(value) WITH ORDINALITY as element (val, n)
WHERE typeof = 'array'
)
)
SELECT DISTINCT key, value #>> '' as value, type
FROM _tree
WHERE key IS NOT NULL
ORDER BY key
Dbfiddle to run.
【讨论】:
以上是关于在 Postgres 中收集递归 JSON 密钥的主要内容,如果未能解决你的问题,请参考以下文章
使用 row_to_json 的 Postgres 递归查询
如何在 Postgres 中选择一列 json 对象,以便返回的行是 json 数组?