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

标签 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

最佳答案

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. 递归部分:与 child 一起对抗 parent
  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    |
  • 现在您有了未嵌套的名称。现在您想要找到必须排除的那些。取 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_andtrue 的元素。当然,您需要在窗口函数中再次执行此操作。这会创建重复记录,可以通过 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/

    相关文章:

    sql - PostgreSQL:用作表达式的子查询返回的多行

    java - 了解递归流程

    algorithm - 最大化给定股票报价的利润

    python - 在python中制作递归函数

    SQLite 到 Postgres 层次结构查询

    postgresql - 如何聚合 Postgres 表以使 ID 唯一并将列值收集在数组中?

    php - 如何使用php在postgresql表中插入timestampz值

    sql - 将 PostgreSQL 的 PL/pgSQL 输出保存到 CSV 文件

    SQL 递归 CTE : preventing a recursive loop by multiple recursive references

    Sql Select 使用 CTE 对递归数据进行排序