Структура дерева и рекурсия
Используя базу данных PostgreSQL 8.4.14, у меня есть таблица, представляющая древовидную структуру, такую как следующий пример:
CREATE TABLE unit (
id bigint NOT NULL PRIMARY KEY,
name varchar(64) NOT NULL,
parent_id bigint,
FOREIGN KEY (parent_id) REFERENCES unit (id)
);
INSERT INTO unit VALUES (1, 'parent', NULL), (2, 'child', 1)
, (3, 'grandchild A', 2), (4, 'grandchild B', 2);
id | name | parent_id
----+--------------+-----------
1 | parent |
2 | child | 1
3 | grandchild A | 2
4 | grandchild B | 2
Я хочу создать список контроля доступа для этих единиц, где каждый элемент может иметь собственный ACL или наследует его от ближайшего предка с собственным ACL.
CREATE TABLE acl (
unit_id bigint NOT NULL PRIMARY KEY,
FOREIGN KEY (unit_id) REFERENCES unit (id)
);
INSERT INTO acl VALUES (1), (4);
unit_id
---------
1
4
Я использую представление, чтобы определить, наследует ли блок ACL от предка:
CREATE VIEW inheriting_acl AS
SELECT u.id AS unit_id, COUNT(a.*) = 0 AS inheriting
FROM unit AS u
LEFT JOIN acl AS a ON a.unit_id = u.id
GROUP BY u.id;
unit_id | inheriting
---------+------------
1 | f
2 | t
3 | t
4 | f
Мой вопрос: как я могу получить ближайшую единицу, которая НЕ наследует ACL от предка? Мой ожидаемый результат должен выглядеть так, как показано в следующей таблице/представлении:
unit_id | acl
---------+------------
1 | 1
2 | 1
3 | 1
4 | 4
Ответы
Ответ 1
Запрос с рекурсивным CTE мог бы выполнить эту работу. Требуется PostgreSQL 8.4 или позже:
WITH RECURSIVE next_in_line AS (
SELECT u.id AS unit_id, u.parent_id, a.unit_id AS acl
FROM unit u
LEFT JOIN acl a ON a.unit_id = u.id
UNION ALL
SELECT n.unit_id, u.parent_id, a.unit_id
FROM next_in_line n
JOIN unit u ON u.id = n.parent_id AND n.acl IS NULL
LEFT JOIN acl a ON a.unit_id = u.id
)
SELECT unit_id, acl
FROM next_in_line
WHERE acl IS NOT NULL
ORDER BY unit_id
Условие прерывания во втором отрезке UNION
составляет n.acl IS NULL
. При этом запрос перестает перемещаться по дереву, как только будет найден acl
.
В финале SELECT
мы возвращаем строки, в которых был найден acl
. Вуаля.
В стороне: анти-шаблон использует общий, не описательный id
как имя столбца. К сожалению, некоторые ORM делают это по умолчанию. Назовите его unit_id
, и вам не нужно постоянно использовать псевдонимы в запросах.