Использование Common Table Expressions (CTE) для поиска в деревьях
Начиная с версии Firebird 2.1 появилась возможность использовать Common Table Expressions (CTE) в запросах, в том числе и для организации рекурсивной обработки информации. Рассмотрим такую задачу: в таблице GD_GOODGROUP находится древовидная иерархия товарных групп. Необходимо построить выборку, которая будет включать:
- все группы с наименованиями, содержащими заданную подстроку;
- для каждой найденной группы из п. 1. все группы из родительской цепочки.
Организация древовидной иерархии в таблице GD_GOODGROUP осуществляется как через ссылку (parent) на идентификатор (id) родительской записи, так и через вложенные интервалы (поля lb и rb).
Знакомство с CTE. Вывод древовидной иерархии на экран
Использование CTE для рекурсивного обхода иерархических структур легче всего понять рассмотрев пример:
WITH RECURSIVE group_tree AS ( SELECT id, parent, name, CAST('' AS VARCHAR(255)) AS indent FROM gd_goodgroup WHERE parent IS NULL UNION ALL SELECT g.id, g.parent, g.name, h.indent || rpad('', 2) FROM gd_goodgroup g JOIN group_tree h ON g.parent = h.id ) SELECT gt.indent || gt.name FROM group_tree gt
Этот запрос выводит древовидный список, используя отступы для выделения вложенных уровней:
Все ТМЦ ТМЦ счет 10.01 ЗЕРНОВЫЕ ОЗИМЫЕ КАРТОФЕЛЬ КОРМА ПОКУПНЫЕ ЗЦМ РАПС ОЗИМЫЙ РАПС ЯРОВОЙ Спецодежда Драгметаллы Основные средства 01. Здания и сооружения 02. Передаточные устр-ва, измерительные приборы ТМЦ счет 10.042
Данное рекурсивное CTE состоит из двух частей. Нерекурсивной, которая создает список корневых элементов:
SELECT id, parent, name, CAST('' AS VARCHAR(255)) AS indent FROM gd_goodgroup WHERE parent IS NULL
и рекурсивной, которая для каждого элемента возвращает список его детей:
SELECT g.id, g.parent, g.name, h.indent || rpad('', 2) FROM gd_goodgroup g JOIN group_tree h ON g.parent = h.id
В общем случае для рекурсивного CTE действуют следующие правила:
- Рекурсивное CTE содержит ссылку на само себя
- Рекурсивное CTE является объединением (UNION) из рекурсивных и нерекурсивных запросов
- Как минимум один нерекурсивный запрос (anchor) должен присутствовать в CTE
- Нерекурсивные запросы располагаются первыми в объединении
- Рекурсивные запросы присоединяются с помощью оператора UNION ALL
- Циклические ссылки не допускаются
- Секции (DISTINCT, GROUP BY, HAVING) и агрегатные функции (SUM, COUNT, MAX etc) не могут присутствовать в рекурсивных запросах
- Рекурсивный запрос может содержать только одну ссылку на себя и только в секции FROM
- Рекурсивные CTE не могут использоваться в OUTER JOIN.
Применение CTE для решения поставленной задачи
В качестве нерекурсивной части CTE мы создадим запрос, который вернет все элементы дерева, удовлетворяющие заданному условию:
SELECT id, parent, name FROM gd_goodgroup WHERE name CONTAINING :S
Добавим рекурсивный запрос, который будет двигаться от найденного элемента последовательно к корню дерева:
SELECT g.id, g.parent, g.name FROM gd_goodgroup g JOIN group_tree h ON g.id = h.parent
Итоговый запрос:
WITH RECURSIVE group_tree AS ( SELECT id, parent, name FROM gd_goodgroup WHERE name CONTAINING :S UNION ALL SELECT g.id, g.parent, g.name FROM gd_goodgroup g JOIN group_tree h ON g.id = h.parent ) SELECT DISTINCT id, parent, name FROM group_tree
Обратите внимание на использование DISTINCT для исключения из выборки элементов дерева, которые могут повторяться при переборе цепочки от каждого найденного элемента к корню.