Ответ вслух Middle Подзапросы и CTE обновлено

Дерево категорий одним запросом

Вопрос

Как бы ты хранил дерево категорий и как одним запросом получить всю ветку под выбранной категорией?

Попробуйте сами

Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.

Показать ответ и разбор

Коротко

По умолчанию храню как adjacency list: у категории есть parentid. Это просто и дёшево менять.

Что должно прозвучать в ответе

  • Простейшее хранение: adjacency list, колонка parent_id

  • Ветка через WITH RECURSIVE: якорь (стартовая категория) UNION ALL шаг (дети найденных)

  • Глубину считать счётчиком в рекурсии, путь собирать массивом

  • Защита от циклов: проверка, что id ещё нет в пути, или ограничение глубины

  • Альтернативы: materialized path (ltree), nested sets, closure table и их цена при изменениях

  • Индекс на parent_id обязателен

Что могут спросить следом

  • Как вывести путь от корня до узла?
  • Что выберешь, если дерево читают в 1000 раз чаще, чем меняют?

Разбор

По умолчанию храню как adjacency list: у категории есть parent_id. Это просто и дёшево менять. Всю ветку достаю рекурсивным CTE: якорь выбирает стартовую категорию, рекурсивная часть присоединяет детей уже найденных, и так пока новых строк нет. Заодно считаю уровень (lvl + 1) и собираю путь массивом path || id, по нему удобно и сортировать, и ловить циклы: WHERE NOT e.id = ANY(path). Нужен индекс на parent_id, иначе каждый шаг рекурсии будет полным сканом. Если дерево огромное и читается гораздо чаще, чем меняется, можно взять materialized path через расширение ltree: поддерево выбирается одним условием по индексу. Или closure table, где хранятся все пары предок-потомок. Платим сложностью переноса веток.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.