如何在树结构中合并JSONB字段?

时间:2015-02-21 16:27:54

标签: postgresql tree recursive-query jsonb postgresql-9.4

我在Postgres中有一个存储树结构的表。每个节点都有一个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);

我需要的是按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"}

1 个答案:

答案 0 :(得分:3)

通常,recursive CTE可以完成这项工作。例如:

我们需要更多魔法来分解,处理和重新组装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;

如何?

  1. 使用经典的递归CTE走树。
  2. LATERAL JOIN中使用jsonb_each_text()创建包含所有键和值的派生表,记住搜索路径中的级别(lvl)。
  3. 使用DISTINCT ON获取每个lvl的“第一个”(最低valuekey。细节:
  4. 对生成的键和值进行排序和汇总,并将数组提供给json_object()以构建最终的params值。
  5. SQL Fiddle(只有第9.3页可以使用json代替jsonb)。