我有一个有父子关系的表。这种关系可以深入 n 层。 还有一个表,其中包含属于某个组的元素。
CREATE TABLE group_children(
id serial PRIMARY KEY,
parent_id integer,
children_id integer,
contains boolean
);
CREATE TABLE group_item(
id serial PRIMARY KEY,
group_id integer,
name text
);
INSERT INTO group_children(parent_id, children_id, contains) VALUES
(1, 2, true),
(1, 3, false),
(2, 4, true),
(2, 5, false),
(3, 6, true),
(3, 7, false);
INSERT INTO group_item(group_id, name) VALUES
(4, 'aaa'),
(4, 'bbb'),
(5, 'bbb'),
(5, 'ccc'),
(6, 'aaa'),
(6, 'bbb'),
(7, 'aaa'),
(7, 'ccc');
因此,我们可以将此数据表示为 不一定是二叉树的形式,简单的情况即可。组可以包含 m 个子项。
需要从右向左阅读。第 4 组包含 ['aaa', 'bbb'],第 5 组 - ['bbb', 'ccc']。第 2 组包括第 4 组中的所有项目,但不包括第 5 组中的所有项目。因此,第 2 组包含 ['aaa']。等等。毕竟计算组 1 将包含 ['aaa']。
问题是:如何构建一个sql查询来获取属于组1的所有项目?
我能做的就是:
WITH RECURSIVE r AS (
SELECT group_children.parent_id, group_children.children_id, group_children.contains, group_item.name
FROM group_children
LEFT JOIN group_item ON group_children.children_id = group_item.group_id
WHERE parent_id = 1
UNION ALL
SELECT group_children.parent_id, group_children.children_id, group_children.contains, group_item.name
FROM group_children
LEFT JOIN group_item ON group_children.children_id = group_item.group_id
JOIN r ON group_children.parent_id = r.children_id
)
SELECT * FROM r;
最佳答案
WITH RECURSIVE items AS (
SELECT -- 1
group_id,
array_agg(name)
FROM
group_Item
GROUP BY group_id
UNION
SELECT DISTINCT
parent_id,
array_agg(unnest) FILTER (WHERE bool_and) OVER (PARTITION BY parent_id) -- 5
FROM (
SELECT
parent_id,
unnest,
bool_and(contains) OVER (PARTITION BY parent_id, unnest) -- 4
FROM items i
JOIN group_children gc -- 2
ON i.group_id = gc.children_id,
unnest(array_agg) -- 3
) s
)
SELECT * FROM items
- 非递归部分聚合每个
group_id
的所有名称 - 递归部分:与 child 一起对抗 parent
- 将名称数组扩展为每行一个元素。
这会导致:
| group_id | array_agg | id | parent_id | children_id | contains | unnest |
|----------|-----------|----|-----------|-------------|----------|--------|
| 4 | {aaa,bbb} | 3 | 2 | 4 | true | aaa |
| 4 | {aaa,bbb} | 3 | 2 | 4 | true | bbb |
| 5 | {bbb,ccc} | 4 | 2 | 5 | false | bbb |
| 5 | {bbb,ccc} | 4 | 2 | 5 | false | ccc |
| 6 | {aaa,bbb} | 5 | 3 | 6 | true | aaa |
| 6 | {aaa,bbb} | 5 | 3 | 6 | true | bbb |
| 7 | {aaa,ccc} | 6 | 3 | 7 | false | aaa |
| 7 | {aaa,ccc} | 6 | 3 | 7 | false | ccc |
- 现在您有了未嵌套的名称。现在您想要找到必须排除的那些。取
parent_id = 2
的 bbb 元素:有一行contains = true
和一行contains = false
。这应该被排除。因此,每个parent_id
的所有名称都必须分组。 contains 值可以使用 bool 运算符进行聚合。如果所有元素都为true
,聚合函数bool_and
仅给出true
。因此bbb
将得到false
(聚合需要以 window function 的形式完成,因为在递归部分中不允许使用GROUP BY
一些原因):
结果:
| parent_id | unnest | bool_and |
|-----------|--------|----------|
| 2 | aaa | true |
| 2 | bbb | false |
| 2 | bbb | false |
| 2 | ccc | false |
| 3 | aaa | false |
| 3 | aaa | false |
| 3 | bbb | true |
| 3 | ccc | false |
- 之后,可以按
parent_id
对未嵌套的名称进行分组。FILTER
子句仅聚合bool_and
为true
的元素。当然,您需要在窗口函数中再次执行此操作。这会创建重复记录,可以通过DISTINCT
子句删除这些重复记录
最终结果(当然可以通过元素1
过滤):
| group_id | array_agg |
|----------|-----------|
| 5 | {bbb,ccc} |
| 4 | {aaa,bbb} |
| 6 | {aaa,bbb} |
| 7 | {aaa,ccc} |
| 2 | {aaa} |
| 3 | {bbb} |
| 1 | {aaa} |
关于postgresql - 包含/排除项目的父/子表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55731094/