2

我在 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"}
4

1 回答 1

3

通常,递归 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. jsonb_each_text()用a中的所有键和值创建一个派生表LATERAL JOIN,记住搜索路径 ( lvl) 中的级别。
  3. 用于DISTINCT ON获取每个. lvl_ 细节: valuekey
  4. 对结果键和值进行排序和聚合,并将数组提供给以json_object()构建最终params值。

SQL Fiddle(仅在 pg 9.3 可以使用json而不是jsonb)。

于 2015-02-22T01:22:48.677 回答