Sử dụng WITH RECURSIVE
(https://www.postgresql.org/docs/current/static/queries-with.html) và Hàm JSON (https://www.postgresql.org/docs/current/static/functions-json.html) I xây dựng giải pháp này:
db <> fiddle
Chức năng cốt lõi:
WITH RECURSIVE tree(node_id, ancestor, child, path, json) AS (
SELECT
t1.node_id,
NULL::int,
t2.node_id,
'{children}'::text[] ||
(row_number() OVER (PARTITION BY t1.node_id ORDER BY t2.node_id) - 1)::text,-- C
jsonb_build_object('name', t2.name, 'children', array_to_json(ARRAY[]::int[])) -- B
FROM test t1
LEFT JOIN test t2 ON t1.node_id = t2.parent_node -- A
WHERE t1.parent_node IS NULL
UNION
SELECT
t1.node_id,
t1.parent_node,
t2.node_id,
tree.path || '{children}' || (row_number() OVER (PARTITION BY t1.node_id ORDER BY t2.node_id) - 1)::text,
jsonb_build_object('name', t2.name, 'children', array_to_json(ARRAY[]::int[]))
FROM test t1
LEFT JOIN test t2 ON t1.node_id = t2.parent_node
INNER JOIN tree ON (t1.node_id = tree.child)
WHERE t1.parent_node = tree.node_id -- D
)
SELECT -- E
child as node_id, path, json
FROM tree
WHERE child IS NOT NULL ORDER BY path
Mỗi WITH RECURSIVE
chứa một SELECT
bắt đầu và một phần đệ quy (SELECT
thứ hai ) được kết hợp bởi một UNION
.
A:Việc tham gia bảng sẽ tự tìm được các phần tử con của một node_id
.
B:Xây dựng đối tượng json cho đối tượng con có thể được chèn vào đối tượng mẹ của nó
C:Xây dựng đường dẫn mà đối tượng con phải được chèn (từ gốc). Hàm cửa sổ row_number()
(https://www.postgresql.org/docs/current/static/tutorial-window.html) tạo chỉ mục con trong mảng con của mảng cha.
D:Phần đệ quy hoạt động như phần ban đầu với một điểm khác biệt:Nó không tìm kiếm phần tử gốc mà tìm phần tử có nút cha của phần đệ quy cuối cùng.
E:Thực thi đệ quy và lọc tất cả các phần tử không có phần tử nào cho kết quả này:
node_id path json
2 children,0 {"name": "node2", "children": []}
4 children,0,children,0 {"name": "node4", "children": []}
5 children,0,children,1 {"name": "node5", "children": []}
6 children,0,children,2 {"name": "node6", "children": []}
3 children,1 {"name": "node3", "children": []}
7 children,1,children,0 {"name": "node7", "children": []}
8 children,1,children,1 {"name": "node8", "children": []}
Mặc dù tôi không tìm thấy cách nào để thêm tất cả các phần tử con trong đệ quy (json gốc không phải là biến toàn cục; vì vậy nó luôn biết những thay đổi của tổ tiên trực tiếp, không phải anh chị em của chúng), tôi đã phải lặp lại các hàng trong một bước giây.
Đó là lý do tại sao tôi xây dựng hàm. Trong đó, tôi có thể thực hiện lặp lại cho một biến toàn cục. Với hàm jsonb_insert
Tôi đang chèn tất cả các phần tử được tính toán vào một đối tượng json gốc - bằng cách sử dụng đường dẫn được tính toán.
CREATE OR REPLACE FUNCTION json_tree() RETURNS jsonb AS $$
DECLARE
_json_output jsonb;
_temprow record;
BEGIN
SELECT
jsonb_build_object('name', name, 'children', array_to_json(ARRAY[]::int[]))
INTO _json_output
FROM test
WHERE parent_node IS NULL;
FOR _temprow IN
/* Query above */
LOOP
SELECT jsonb_insert(_json_output, _temprow.path, _temprow.json) INTO _json_output;
END LOOP;
RETURN _json_output;
END;
$$ LANGUAGE plpgsql;
Bước cuối cùng là gọi hàm và làm cho JSON dễ đọc hơn (jsonb_pretty()
)
{
"name": "node1",
"children": [{
"name": "node2",
"children": [{
"name": "node4",
"children": []
},
{
"name": "node5",
"children": []
},
{
"name": "node6",
"children": []
}]
},
{
"name": "node3",
"children": [{
"name": "node7",
"children": []
},
{
"name": "node8",
"children": []
}]
}]
}
Tôi chắc chắn rằng có thể tối ưu hóa truy vấn nhưng đối với một bản phác thảo thì nó hoạt động.