Условие
Таблица employees(employee_id, name, manager_id) — у каждого сотрудника один менеджер (или NULL для CEO). Для каждого сотрудника верните полную цепочку менеджеров (от прямого руководителя до CEO) в виде строки через →.
Решение
Рекурсивный CTE
WITH RECURSIVE chain AS (
SELECT
employee_id, name, manager_id,
name::TEXT AS path,
1 AS depth
FROM employees
UNION ALL
SELECT
e.employee_id, e.name, m.manager_id,
c.path || ' → ' || m.name,
c.depth + 1
FROM chain c
JOIN employees m ON m.employee_id = c.manager_id
JOIN employees e ON e.employee_id = c.employee_id
)
SELECT employee_id, name, path AS manager_chain
FROM chain
WHERE manager_id IS NULL -- остановились на CEO
ORDER BY employee_id;Объяснение
- Базовый кейс: каждый сотрудник — стартовая точка,
path = name. - Рекурсия: переходим к менеджеру, добавляем его в путь.
- Останов: когда
manager_id IS NULL— добрались до CEO.
Защита от циклов
Если в данных есть зацикленность (A — менеджер B, B — менеджер A), рекурсия уйдёт в бесконечность. Postgres имеет CYCLE clause (с 14):
WITH RECURSIVE chain AS (...)
CYCLE employee_id SET is_cycle USING path_arrВ MySQL и старом Postgres — ограничивать через depth < 100.
Подводные камни
- Базовый кейс не для CEO. Стартуем со ВСЕХ сотрудников, не с CEO — иначе сложно собрать путь «снизу вверх».
UNIONvsUNION ALL. ТолькоUNION ALL— иначе оптимизатор может схлопнуть строки.- Сборка пути в обратном порядке. Здесь конкатенируем
c.path || ' → ' || m.name— путь идёт от сотрудника вверх. Если просят сверху вниз — собирать иначе или строить массив и реверсить. - Производительность. Рекурсивные CTE плохо масштабируются на больших иерархиях. На 1М сотрудников — не вариант, делать MATERIALIZED hierarchy table.
Эталонный ответ
WITH RECURSIVE, базовый = все, шаг = JOIN с менеджером, останов по manager_id IS NULL. Конкатенация строки или массива по пути.