Parent/child table 有 include/exclude 项

Parent/child table with include/exclude items

我有 table 和 parent-child 关系。关系可以深入n-level。 还有一个 table 包含属于一个组的元素。

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');

因此,我们可以将此数据表示为 不一定是二叉树的形式,简单的case即可。组可以包含 m child.

需要从右到左阅读。第 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;

SQL Fiddle

demo:db<>fiddle

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
  1. non-recursive 部分汇总了每个 group_id
  2. 的所有名称
  3. 递归部分:加入孩子对抗他们的 parents
  4. 将名称数组扩展为每行一个元素。

这导致:

| 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    |
  1. 现在您有了未嵌套的名称。现在您想找到必须排除的那些。以 parent_id = 2 的 bbb 元素为例: contains = true 一行,contains = false 一行。这应该被排除在外。因此,每个 parent_id 的所有名称都必须分组。包含的值可以使用布尔运算符进行聚合。如果所有元素都是 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    |
  1. 之后,未嵌套的名称可以按照 parent_id 进行分组。 FILTER 子句仅聚合 bool_andtrue 的元素。当然,您需要在 window 函数中再次执行此操作。这会创建重复的记录,可以通过 DISTINCT 子句
  2. 删除

最终结果(当然可以通过元素1过滤):

| group_id | array_agg |
|----------|-----------|
|        5 | {bbb,ccc} |
|        4 | {aaa,bbb} |
|        6 | {aaa,bbb} |
|        7 | {aaa,ccc} |
|        2 | {aaa}     |
|        3 | {bbb}     |
|        1 | {aaa}     |