如何连接嵌套的jsonb数组元素?

时间:2017-11-14 00:09:09

标签: json postgresql aggregate-functions jsonb postgresql-9.6

我的问题与此问题有些类似:
How to join jsonb array elements in Postgres?

但是我需要填写一些嵌套数组。为了保持简单,我只有一个表:

CREATE table tester(
  id int,
  name text,
  d jsonb  
)

INSERT INTO tester(id, name, d) VALUES
  ('1', 'bob',    '[
                     {
                       "employees": [{"id":2},{"id":3},{"id":4}],
                       "coworkers": [{"id":5},{"id":6}]
                     },
                     {
                       "employees": [{"id":3},{"id":4}],
                       "coworkers": [{"id":5}]
                     }
                   ]'::jsonb),
   ('2', 'barb',    '[
                     {
                       "employees": [{"id":3}],
                       "coworkers": []
                     },
                     {
                       "employees": [{"id":3},{"id":4}],
                       "coworkers": [{"id":5, "id":3}]
                     }
                   ]'::jsonb),

   ('3', 'ann',    '[]'::jsonb),
   ('4', 'jeff',   '[]'::jsonb),
   ('5', 'rachel', '[]'::jsonb),
   ('6', 'ryan',   '[]'::jsonb);

请参阅:http://sqlfiddle.com/#!17/7c7ef/12/0

我试图简单地为每个同事和员工添加名称,以便bob看起来像:

[
  {
    "employees": [{"id":2, "name":"barb"},{"id":3, "name":"ann"},{"id":4, "jeff"}],
    "coworkers": [{"id":5, "name":"rachel"},{"id":6, "name":"ryan"}]
  },
  {
    "employees": [{"id":3, "name":"ann"},{"id":4, "name":"jeff"}],
    "coworkers": [{"id":5, "name":"rachel"}]
  }
]

到目前为止,我有:

SELECT c.person person
FROM tester
LEFT JOIN LATERAL(
    SELECT jsonb_agg(
        jsonb_build_object(
            'employees', c.wrk->'employees',
            'coworkers', c.wrk->'coworkers'
        )
    ) AS person
    FROM jsonb_array_elements(tester.d) AS c(wrk)
) c ON true

除了名字之外,它会返回所有内容:

[{"coworkers": [{"id": 5}, {"id": 6}], "employees": [{"id": 2}, {"id": 3}, {"id": 4}]}, {"coworkers": [{"id": 5}], "employees": [{"id": 3}, {"id": 4}]}]
[{"coworkers": [], "employees": [{"id": 3}]}, {"coworkers": [{"id": 3}], "employees": [{"id": 3}, {"id": 4}]}]
(null)
(null)
(null)
(null)

请注意对象列表:它们是单独的对象,而不仅仅是一个大对象。

"(null)" s / b一个空白数组" []"。

2 个答案:

答案 0 :(得分:0)

使用两个横向连接,我们可以为同事和员工创建数组,我们在横向查询中连接到tester表以获取名称,然后构造jsonb对象&聚合以获得转换后的数组。

结果查询很多,但并不过分复杂。

SELECT 
    "name"
  , CASE 
      WHEN d = '[]'::jsonb THEN NULL 
      ELSE coworker.people || employee.people 
    END relationships
FROM tester
, LATERAL (
    SELECT 
        jsonb_build_object(
             'coworkers'
           , JSON_AGG(json_build_object('id', id, 'name', "name"))
        ) people
    FROM (SELECT DISTINCT 
              (jsonb_array_elements(el->'coworkers')->>'id')::int id
          FROM jsonb_array_elements(d) el) coworker
    NATURAL JOIN tester
    ) coworker
, LATERAL (
    SELECT 
        jsonb_build_object(
           'employees'
          , JSON_AGG(json_build_object('id', id, 'name', "name"))) people
    FROM (SELECT DISTINCT 
              (jsonb_array_elements(el->'employees')->>'id')::int id
          FROM jsonb_array_elements(d) el) employee
    NATURAL JOIN tester
    ) employee

对象列表的替代解决方案:

WITH people_separated AS (
  SELECT 
    "name"
  , coworkers
  , employees
  FROM tester
  , LATERAL (
    SELECT
      k->'coworkers' coworkers
    , k->'employees' employees 
    FROM jsonb_array_elements(d) k
  ) split
) 
, people_relationships AS (
SELECT 
  name
, JSON_AGG(
    CASE WHEN e.people IS NULL THEN '{}'::jsonb ELSE jsonb_build_object('employees', e.people) END
    || 
    CASE WHEN c.people IS NULL THEN '{}'::jsonb ELSE jsonb_build_object('coworkers', c.people) END
  ) relationships
FROM people_separated
, LATERAL (
    SELECT JSON_AGG(
        c || jsonb_build_object(
                  'name'
                , (SELECT name FROM tester WHERE id = (c->>'id')::int)
             )
    ) people 
    FROM jsonb_array_elements(coworkers) c) c
, LATERAL (
    SELECT JSON_AGG(
        e || jsonb_build_object(
                  'name'
                , (SELECT name FROM tester WHERE id = (e->>'id')::int)
             )
    ) people
    FROM jsonb_array_elements(employees) e) e
GROUP BY 1
)
SELECT name, relationships FROM tester LEFT JOIN people_relationships USING (name)

答案 1 :(得分:0)

假设tester.id是PK,以简化聚合:

SELECT t.id, t.name, COALESCE(t1.d, t.d)
FROM   tester t
LEFT   JOIN LATERAL (
   SELECT jsonb_agg(jsonb_build_object('coworkers', COALESCE(c.coworkers, jsonb '[]'))
                 || jsonb_build_object('employees', COALESCE(e.employees, jsonb '[]'))) AS d
   FROM   jsonb_array_elements(t.d) AS d1(p)
   CROSS  JOIN LATERAL (
      SELECT jsonb_agg(p.id || jsonb_build_object('name', n.name)) AS coworkers
      FROM   jsonb_array_elements(d1.p ->'coworkers') AS p(id)
      LEFT   JOIN tester n ON n.id = (p.id->>'id')::int
      ) c
   CROSS  JOIN LATERAL (
      SELECT jsonb_agg(p.id || jsonb_build_object('name', n.name)) AS employees
      FROM   jsonb_array_elements(d1.p ->'employees') AS p(id)
      LEFT   JOIN tester n ON n.id = (p.id->>'id')::int
      ) e
   GROUP  BY t.id
   ) t1 ON t.d <> '[]';

SQL Fiddle.

解释与我引用的旧答案很相似:

一个特殊的难点是保留空JSON数组'[]',其中聚合将返回NULL值,我使用COALESCE()的战略用途解决了这个问题。

另一个是你希望将嵌套数组分开。解决了将未版本化阵列聚合回JSON数组的问题,在同事和员工的两个独立LATERAL联接中。

请注意您的数据中的陷阱"coworkers": [{"id":5, "id":3}]

SELECT jsonb '[{"id":5, "id":3}]'会产生'[{"id": 3}]'。也许你打算写'[{"id":5}, {"id":3}]'