【问题标题】:How to join nested jsonb array elements?如何加入嵌套的 jsonb 数组元素?
【发布时间】:2018-04-26 19:13:18
【问题描述】:

我的问题和这个有点类似:
How to join jsonb array elements in Postgres?

但我需要填写一些嵌套数组。为简单起见,我只有 1 个表:

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 是一个空白数组 "[]"。

【问题讨论】:

  • 附加服务功能可能更简单:dbfiddle.uk/…
  • 我认为您需要为每个同事和员工提供两个横向连接

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


【解决方案1】:

假设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 连接中解决了这个问题。


注意数据中的 trap 用于倒钩:"coworkers": [{"id":5, "id":3}]

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

【讨论】:

    【解决方案2】:

    使用两个横向连接,我们可以为同事和员工创建数组,我们在横向查询中连接到 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)
    

    【讨论】:

    • 这有点像,但只给出了一个对象。我需要一个对象列表。 sqlfiddle.com/#!17/7c7ef/15/0
    • 答案已更新,原理相同,只是在不同级别应用了聚合
    • 差不多了,但它忽略了 barb 的一位同事:sqlfiddle.com/#!17/7c7ef/16/0
    • 原来我的问题 re: barb 的同事有一个错误。我的错。您收到的另一个答案的赏金。谢谢。
    • 谢谢,这不是必须的,你太慷慨了。
    猜你喜欢
    • 2017-03-17
    • 2017-03-28
    • 1970-01-01
    • 2017-04-12
    • 1970-01-01
    • 2019-09-12
    • 1970-01-01
    • 2019-09-05
    • 2019-05-29
    相关资源
    最近更新 更多