Строим иерархию объектов

Иерархическая структура данных

Посмотрим внимательно на таблицу категорий товаров

select *
  from category
# category_id parent_category_id name
1 1 NULL Товары для дома
2 2 NULL Цифровая техника
3 3 1 Бытовая техника
4 4 2 Ноутбуки и аксессуары
5 5 2 Фотоаппараты
6 6 2 Игровые консоли
7 7 2 Аудиотехника
8 8 2 Сотовые телефоны
9 9 4 Ноутбуки
10 10 4 Рюкзаки

Каждая строка хранит ссылку на родительский каталог parent_category_id. Если родительского каталога нет (каталог является корневым), то parent_category_id принимает значение NULL. Это типовая структура для хранения иерархических данных в реляционных базах данных.

Таким образом каталоги хранят в себе подкаталоги:

Товары для дома
      Бытовая техника
Цифровая техника
      Ноутбуки и аксессуары
            Ноутбуки
            Рюкзаки
      Фотоаппараты
      Игровые консоли
      Аудиотехника
      Сотовые телефоны

Давай научимся выводить иерархические данные с помощью SQL запросов.

Для каждого каталога выведем его уровень вложенности.

Для этого получим в нерекурсивной части SQL запроса каталоги, у которых нет родителя. Будем их считать каталогами 1-го уровня:

WITH RECURSIVE lv_hierarchy AS (
  SELECT c.category_id,
         c.parent_category_id,
         c.name,
         1 AS level
    FROM category c
   WHERE c.parent_category_id IS NULL
)
SELECT *
  FROM lv_hierarchy
# category_id parent_category_id name level
1 1 NULL Товары для дома 1
2 2 NULL Цифровая техника 1

Теперь с помощью рекурсивной части подзапроса будем добавлять вложенные каталоги.

WITH RECURSIVE lv_hierarchy AS (
  SELECT c.category_id,
         c.parent_category_id,
         c.name,
         1 AS level
    FROM category c
   WHERE c.parent_category_id IS NULL

   UNION ALL

  SELECT c.category_id,
         c.parent_category_id,
         c.name,
         p.level + 1 AS level
    FROM lv_hierarchy p,
         category c
   WHERE c.parent_category_id = p.category_id
)
SELECT *
  FROM lv_hierarchy
# category_id parent_category_id name level
1 1 NULL Товары для дома 1
2 2 NULL Цифровая техника 1
3 3 1 Бытовая техника 2
4 4 2 Ноутбуки и аксессуары 2
5 5 2 Фотоаппараты 2
6 6 2 Игровые консоли 2
7 7 2 Аудиотехника 2
8 8 2 Сотовые телефоны 2
9 9 4 Ноутбуки 3
10 10 4 Рюкзаки 3

Разберем рекурсивную часть подзапроса

  SELECT c.category_id,
         c.parent_category_id,
         c.name,
         p.level + 1 AS level
    FROM lv_hierarchy p,
         category c
   WHERE c.parent_category_id = p.category_id

В lv_hierarchy p (временная таблица, см. предыдущее задание) лежат строки, полученные в результате:

  1. Нерекурсивной части подзапроса для первой итерации;
  2. Предыдущей итерации рекурсивной части.

Это каталоги, для которых ищем дочерние на текущей итерации. Псевдоним p дан таблице по первой букве от слова parent.

К родительским каталогам присоединяем таблицу category c по условию c.parent_category_id = p.category_id: для каждого родителя находим дочерние каталоги, в которых идентификатор родительского каталога parent_category_id равен идентификатору записи текущего родителя.

Итоги

Мы получили уровень вложенности каждого каталога. Но в данном решении есть несколько проблем:

  • Строки отсортированы в порядке их получения. Хотелось бы видеть дочерние строки одного родителя рядом.
  • Уровень вложенности не виден визуально.
  • Не показан путь (хлебные крошки до каталога).

10.5 Рекурсивный запрос посложнее

10.7 Путь до элемента