如何加入嵌套的 jsonb 数组元素?
How to join nested jsonb array elements?
我的问题和这个有点相似:
但是我有一些嵌套数组需要填写。为了简单起见,我只有 1 table:
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
我想简单地为每个同事和雇员添加名字,这样鲍勃看起来像:
[
{
"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
returns 除了名字以外的所有内容:
[{"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一个空白数组“[]”。
使用两个横向连接,我们可以为同事和员工创建数组,我们在横向查询中连接到测试人员 table 以获取名称,然后构造 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)
假设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 <> '[]';
解释与您引用的我的旧答案非常相似:
一个特殊的困难是保留空 JSON 数组 '[]'
,其中聚合将 returns NULL 值,我通过策略性使用 COALESCE()
解决了这个问题。
另一个是您希望将嵌套数组分开。通过将未嵌套的数组直接聚合回 JSON 数组,在同事和员工的两个单独的 LATERAL
连接中解决了这个问题。
注意倒钩数据中的陷阱:"coworkers": [{"id":5, "id":3}]
SELECT jsonb '[{"id":5, "id":3}]'
结果为 '[{"id": 3}]'
。也许你打算写 '[{"id":5}, {"id":3}]'
?
我的问题和这个有点相似:
但是我有一些嵌套数组需要填写。为了简单起见,我只有 1 table:
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
我想简单地为每个同事和雇员添加名字,这样鲍勃看起来像:
[
{
"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
returns 除了名字以外的所有内容:
[{"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一个空白数组“[]”。
使用两个横向连接,我们可以为同事和员工创建数组,我们在横向查询中连接到测试人员 table 以获取名称,然后构造 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)
假设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 <> '[]';
解释与您引用的我的旧答案非常相似:
一个特殊的困难是保留空 JSON 数组 '[]'
,其中聚合将 returns NULL 值,我通过策略性使用 COALESCE()
解决了这个问题。
另一个是您希望将嵌套数组分开。通过将未嵌套的数组直接聚合回 JSON 数组,在同事和员工的两个单独的 LATERAL
连接中解决了这个问题。
注意倒钩数据中的陷阱:"coworkers": [{"id":5, "id":3}]
SELECT jsonb '[{"id":5, "id":3}]'
结果为 '[{"id": 3}]'
。也许你打算写 '[{"id":5}, {"id":3}]'
?