包含/排除项目的父/子表

时间:2019-04-17 15:24:17

标签: postgresql recursion recursive-query

我有一张有亲子关系的桌子。这种关系可以达到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');

因此,我们可以将该数据表示为enter image description here 不必采用二叉树的形式,只是一个简单的情况。群组可以包含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;

SQL Fiddle

1 个答案:

答案 0 :(得分:2)

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. 非递归部分按group_id聚合所有名称
  2. 递归部分:加入孩子们对抗父母的生活
  3. 将名称数组扩展为每行一个元素。

结果是:

| 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的名称都必须分组。包含值可以使用布尔运算符聚合。如果所有元素均为bool_and,则聚合函数true仅给出true。因此bbb将得到一个false(由于某些原因不允许在递归部分中使用GROUP BY,因此聚合需要以window function的形式进行):

结果:

| 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的元素。当然,您需要再次在窗口函数中执行此操作。这样会创建重复记录,可以通过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}     |