59. Спроектировать таблицу для иерархического справочника организаций
Условие задачи:
Необходимо спроектировать структуру БД для иерархического справочника подразделений.
Структура может иметь произвольную глубину, например:
ТН - Технологии
Управление ИБ
Отдел разработки решений ИБ
Бюро разработки
Бюро эксплуатации
Управление ИТ
Отдел ИТ
Отдел архитектуры
ТН - Дальний восток
РНУ
НПС 1
НПС 2
Нужно определить:
достаточно ли одной таблицы;
как хранить связь между родительским и дочерним подразделением;
какие основные поля понадобятся.
Спойлеры к решению
Подсказки
Adjacency List.💡 Каждая строка представляет один узел дерева и содержит ссылку
parent_id на родительский узел.💡 У корневых элементов
parent_id = NULL.💡 Необязательно создавать отдельные таблицы для управлений, отделов, бюро и других уровней.
💡 Для быстрого поиска непосредственных потомков полезен индекс по
parent_id.Решение
Для такого справочника обычно достаточно одной таблицы, которая ссылается сама на себя через parent_id.
CREATE TABLE org_unit (
id BIGSERIAL PRIMARY KEY,
parent_id BIGINT REFERENCES org_unit(id),
name VARCHAR(255) NOT NULL,
type VARCHAR(50),
code VARCHAR(50),
sort_order INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (parent_id IS NULL OR parent_id <> id)
);
Для поиска дочерних подразделений стоит добавить индекс:
CREATE INDEX idx_org_unit_parent_id
ON org_unit (parent_id);
Пример хранения дерева:
id | parent_id | name | type
---+-----------+-----------------------------+-----------
1 | NULL | ТН - Технологии | HOLDING
2 | 1 | Управление ИБ | MANAGEMENT
3 | 2 | Отдел разработки решений ИБ | DEPARTMENT
4 | 3 | Бюро разработки | BUREAU
5 | 3 | Бюро эксплуатации | BUREAU
6 | 1 | Управление ИТ | MANAGEMENT
7 | 6 | Отдел ИТ | DEPARTMENT
8 | 6 | Отдел архитектуры | DEPARTMENT
9 | NULL | ТН - Дальний восток | HOLDING
10 | 9 | РНУ | MANAGEMENT
11 | 10 | НПС 1 | NPS
12 | 10 | НПС 2 | NPS
Например, чтобы получить непосредственных потомков узла:
SELECT
id,
name,
type
FROM org_unit
WHERE parent_id = 1
ORDER BY sort_order, name;
Для получения всего поддерева можно использовать рекурсивный CTE:
WITH RECURSIVE tree AS (
SELECT
id,
parent_id,
name,
type,
0 AS depth
FROM org_unit
WHERE id = 1
UNION ALL
SELECT
child.id,
child.parent_id,
child.name,
child.type,
tree.depth + 1
FROM org_unit child
JOIN tree
ON child.parent_id = tree.id
)
SELECT *
FROM tree
ORDER BY depth, id;
Хранить level в самой таблице обычно необязательно: глубину узла можно вычислить при обходе дерева. Отдельное поле level имеет смысл добавлять только как денормализованное значение, если это действительно требуется для производительности.
Если в одной БД нужно хранить несколько независимых организаций как отдельные бизнес-сущности, можно дополнительно создать таблицу organization и добавить в org_unit поле organization_id.
Основная идея схемы:
org_unit
│
├── id
├── parent_id ──→ org_unit.id
├── name
├── type
└── ...
Такая модель позволяет добавлять новые уровни иерархии без изменения структуры таблиц.