我在Postgres 11.4数据库中有一个自带树形结构的表。
+------------+
| account |
+------------+
| id |
| code |
| type |
| parentId | -- references account.id
+------------+
每个子代都可以有另一个子代,嵌套级别没有限制。
我想从中生成一个单一的JSON对象,嵌套所有的子对象(resursivly)。
是否有可能用单一查询来解决这个问题? 或者使用typeORM与一个表的任何其他解决方案?否则我将不得不在服务器端手动绑定数据。
我尝试了这个查询。
SELECT account.type, json_agg(account) as accounts
FROM account
-- LEFT JOIN "account" "child" ON "child"."parentId"="account"."id" -- tried to make one column child
GROUP BY account.type
结果:
[
...
{
"type": "type03",
"accounts": [
{
"id": 28,
"code": "acc03.001",
"type": "type03",
"parentId": null
},
{
"id": 29,
"code": "acc03.001.001",
"type": "type03",
"parentId": 28
},
{
"id": 30,
"code": "acc03.001.002",
"type": "type03",
"parentId": 28
}
]
}
...
]
我希望得到这样的结果
[
...
{
"type": "type03",
"accounts": [
{
"id": 28,
"code": "acc03.001",
"type": "type03",
"parentId": null,
"child": [
{
"id": 29,
"code": "acc03.001.001",
"type": "type03",
"parentId": 28
},
{
"id": 30,
"code": "acc03.001.002",
"type": "type03",
"parentId": 28
}
]
}
]
}
...
]
调用(准确返回所需结果)。db<>fiddle 此处
- 带扩展测试用例我选择了一个关键的名字 而不是
,因为多个可以嵌套。
CREATE OR REPLACE FUNCTION f_build_jsonb_tree(_type text = NULL)
RETURNS jsonb
LANGUAGE plpgsql AS
$func$
DECLARE
_nest_lvl int;
BEGIN
-- add level of nesting recursively
CREATE TEMP TABLE t ON COMMIT DROP AS
WITH RECURSIVE t AS (
SELECT *, 1 AS lvl
FROM account
WHERE "parentId" IS NULL
AND (type = _type OR _type IS NULL) -- default: whole table
UNION ALL
SELECT a.*, lvl + 1
FROM t
JOIN account a ON a."parentId" = t.id
)
TABLE t;
-- optional idx for big tables with many levels of nesting
-- CREATE INDEX ON t (lvl, id);
_nest_lvl := (SELECT max(lvl) FROM t);
-- no nesting found, return simple result
IF _nest_lvl = 1 THEN
RETURN ( -- exits functions
SELECT jsonb_agg(sub) -- AS result
FROM (
SELECT type
, jsonb_agg(sub) AS accounts
FROM (
SELECT id, code, type, "parentId", NULL AS children
FROM t
ORDER BY type, id
) sub
GROUP BY 1
) sub
);
END IF;
-- start collapsing with leaves at highest level
CREATE TEMP TABLE j ON COMMIT DROP AS
SELECT "parentId" AS id
, jsonb_agg (sub) AS children
FROM (
SELECT id, code, type, "parentId" -- type redundant?
FROM t
WHERE lvl = _nest_lvl
ORDER BY id
) sub
GROUP BY "parentId";
-- optional idx for big tables with many levels of nesting
-- CREATE INDEX ON j (id);
-- iterate all the way down to lvl 2
-- write to same table; ID is enough to identify
WHILE _nest_lvl > 2
LOOP
_nest_lvl := _nest_lvl - 1;
INSERT INTO j(id, children)
SELECT "parentId" -- AS id
, jsonb_agg(sub) -- AS children
FROM (
SELECT id, t.code, t.type, "parentId", j.children -- type redundant?
FROM t
LEFT JOIN j USING (id) -- may or may not have children
WHERE t.lvl = _nest_lvl
ORDER BY id
) sub
GROUP BY "parentId";
END LOOP;
-- nesting found, return nested result
RETURN ( -- exits functions
SELECT jsonb_agg(sub) -- AS result
FROM (
SELECT type
, jsonb_agg (sub) AS accounts
FROM (
SELECT id, code, type, "parentId", j.children
FROM t
LEFT JOIN j USING (id)
WHERE t.lvl = 1
ORDER BY type, id
) sub
GROUP BY 1
) sub
);
END
$func$;
来美化显示是可选的。
SELECT jsonb_pretty(f_build_jsonb_tree());
这是在假设引用完整性的前提下,应该用FK约束来实现。对于你的特殊情况,解决方案可能更简单,利用 列--如果它表现出(未披露的)有用的属性。就像我们可能在没有rCTE的情况下推导出嵌套层,并添加临时表
. 但我的目标是 children
通则child
仅基于ID引用。
jsonb_pretty()
函数里有很多内容。我添加了内联注释。基本上,它是这样做的。
创建一个临时表,并添加嵌套级别(
)code
如果没有找到嵌套,返回简单结果。t
如发现窝点,则塌陷到 从最高嵌套层向下。将所有的中间结果写入第二个临时表中 .
一旦我们到达第二个嵌套层,就返回全部结果。
lvl
作为参数,只返回给定类型。否则,整个表都会被处理。jsonb
PostgreSQL的列名是否区分大小写?j