Родительский / дочерний стол с элементами include / exclude - PullRequest
2 голосов
/ 17 апреля 2019

У меня есть таблица с отношениями родитель-ребенок.Отношения могут идти 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 Ответ

2 голосов
/ 18 апреля 2019

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    |
Теперь у вас есть неопубликованные имена.Теперь вы хотите найти те, которые должны быть исключены.Взяв элемент bbb для parent_id = 2: есть одна строка с contains = true и одна с contains = false.Это должно быть исключено.Поэтому все имена в parent_id должны быть сгруппированы.Содержащие значения могут быть агрегированы с логическими операторами.Агрегирующая функция bool_and дает только true, если все элементы true.Так что bbb получит false (агрегация должна выполняться как оконная функция , потому что 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}     |
...