如何在树结构中合并 JSONB 字段?
How to merge JSONB field in a tree structure?
我在 Postgres 中有一个 table 存储树结构。每个节点都有一个jsonb
字段:params_diff
:
CREATE TABLE tree (id INT, parent_id INT, params_diff JSONB);
INSERT INTO tree VALUES
(1, NULL, '{ "some_key": "some value" }'::jsonb)
, (2, 1, '{ "some_key": "other value", "other_key": "smth" }'::jsonb)
, (3, 2, '{ "other_key": "smth else" }'::jsonb);
我需要的是 select id
的一个节点,附加生成的 params
字段包含合并整个父链中所有 params_diff
的结果:
SELECT tree.*, /* some magic here */ AS params FROM tree WHERE id = 3;
id | parent_id | params_diff | params
----+-----------+----------------------------+-------------------------------------------------------
3 | 2 | {"other_key": "smth else"} | {"some_key": "other value", "other_key": "smth else"}
一般来说,一个 recursive CTE 就可以完成这项工作。示例:
- Use table alias in another query to traverse a tree
我们只需要更多的魔法来分解、处理和重新assemble JSON 结果。我从您的示例中假设,您只需要每个键一次,搜索路径中的第一个值(自下而上):
WITH RECURSIVE cte AS (
SELECT id, parent_id, params_diff, 1 AS lvl
FROM tree
WHERE id = 3
UNION ALL
SELECT t.id, t.parent_id, t.params_diff, c.lvl + 1
FROM cte c
JOIN tree t ON t.id = c.parent_id
)
SELECT id, parent_id, params_diff
, (SELECT json_object(array_agg(key ORDER BY lvl)
, array_agg(value ORDER BY lvl))::jsonb
FROM (
SELECT key, value
FROM (
SELECT DISTINCT ON (key)
p.key, p.value, c.lvl
FROM cte c, jsonb_each_text(c.params_diff) p
ORDER BY p.key, c.lvl
) sub1
ORDER BY lvl
) sub2
) AS params
FROM cte
WHERE id = 3;
如何?
- 使用经典的递归 CTE 遍历树。
- 在
LATERAL JOIN
中创建一个包含所有键和值 jsonb_each_text()
的派生 table,记住搜索路径中的级别 (lvl
)。
- 使用
DISTINCT ON
得到每个 key
的 "first"(最低 lvl
)value
。细节:
- Select first row in each GROUP BY group?
- 对生成的键和值进行排序和聚合,并将数组提供给
json_object()
以构建最终的 params
值。
SQL Fiddle(只有第 9.3 页可以使用 json
而不是 jsonb
)。
我在 Postgres 中有一个 table 存储树结构。每个节点都有一个jsonb
字段:params_diff
:
CREATE TABLE tree (id INT, parent_id INT, params_diff JSONB);
INSERT INTO tree VALUES
(1, NULL, '{ "some_key": "some value" }'::jsonb)
, (2, 1, '{ "some_key": "other value", "other_key": "smth" }'::jsonb)
, (3, 2, '{ "other_key": "smth else" }'::jsonb);
我需要的是 select id
的一个节点,附加生成的 params
字段包含合并整个父链中所有 params_diff
的结果:
SELECT tree.*, /* some magic here */ AS params FROM tree WHERE id = 3;
id | parent_id | params_diff | params
----+-----------+----------------------------+-------------------------------------------------------
3 | 2 | {"other_key": "smth else"} | {"some_key": "other value", "other_key": "smth else"}
一般来说,一个 recursive CTE 就可以完成这项工作。示例:
- Use table alias in another query to traverse a tree
我们只需要更多的魔法来分解、处理和重新assemble JSON 结果。我从您的示例中假设,您只需要每个键一次,搜索路径中的第一个值(自下而上):
WITH RECURSIVE cte AS (
SELECT id, parent_id, params_diff, 1 AS lvl
FROM tree
WHERE id = 3
UNION ALL
SELECT t.id, t.parent_id, t.params_diff, c.lvl + 1
FROM cte c
JOIN tree t ON t.id = c.parent_id
)
SELECT id, parent_id, params_diff
, (SELECT json_object(array_agg(key ORDER BY lvl)
, array_agg(value ORDER BY lvl))::jsonb
FROM (
SELECT key, value
FROM (
SELECT DISTINCT ON (key)
p.key, p.value, c.lvl
FROM cte c, jsonb_each_text(c.params_diff) p
ORDER BY p.key, c.lvl
) sub1
ORDER BY lvl
) sub2
) AS params
FROM cte
WHERE id = 3;
如何?
- 使用经典的递归 CTE 遍历树。
- 在
LATERAL JOIN
中创建一个包含所有键和值jsonb_each_text()
的派生 table,记住搜索路径中的级别 (lvl
)。 - 使用
DISTINCT ON
得到每个key
的 "first"(最低lvl
)value
。细节:- Select first row in each GROUP BY group?
- 对生成的键和值进行排序和聚合,并将数组提供给
json_object()
以构建最终的params
值。
SQL Fiddle(只有第 9.3 页可以使用 json
而不是 jsonb
)。