Для MySQL 8+: используйте рекурсивный with
синтаксис.
Для MySQL 5.x: используйте встроенные переменные, идентификаторы пути или самостоятельные соединения.
MySQL 8+
with recursive cte (id, name, parent_id) as (
select id,
name,
parent_id
from products
where parent_id = 19
union all
select p.id,
p.name,
p.parent_id
from products p
inner join cte
on p.parent_id = cte.id
)
select * from cte;
Значение, указанное в, parent_id = 19
должно быть установлено на id
родительского элемента, для которого вы хотите выбрать всех потомков.
MySQL 5.x
Для версий MySQL, которые не поддерживают Common Table Expressions (до версии 5.7), этого можно достичь с помощью следующего запроса:
select id,
name,
parent_id
from (select * from products
order by parent_id, id) products_sorted,
(select @pv := '19') initialisation
where find_in_set(parent_id, @pv)
and length(@pv := concat(@pv, ',', id))
Вот скрипка .
Здесь значение, указанное в, @pv := '19'
должно быть установлено на id
родительского элемента, для которого вы хотите выбрать всех потомков.
Это также будет работать, если у родителя несколько детей. Однако требуется, чтобы каждая запись удовлетворяла условию parent_id < id
, иначе результаты не будут полными.
Переменные в запросе
Этот запрос использует определенный синтаксис MySQL: переменные назначаются и изменяются во время его выполнения. Некоторые предположения сделаны относительно порядка исполнения:
- Предложение
from
оценивается первым. Так вот где @pv
инициализируется.
- Предложение
where
оценивается для каждой записи в порядке извлечения из from
псевдонимов. Таким образом, именно здесь ставится условие включения только тех записей, для которых родительский объект уже был идентифицирован как находящийся в дереве потомков (все потомки первичного родителя постепенно добавляются @pv
).
- Условия в этом
where
пункте оцениваются по порядку, и оценка прерывается, как только общий результат определен. Поэтому второе условие должно быть на втором месте, так как оно добавляет id
к родительскому списку, и это должно произойти, только если id
проходит первое условие. length
Функция вызывается только , чтобы убедиться , что это условие всегда истинно, даже если pv
строка будет по какой - то причине дают falsy значение.
В целом, эти предположения могут оказаться слишком рискованными, чтобы на них можно было положиться. Документация предупреждает:
вы можете получить ожидаемые результаты, но это не гарантируется [...] порядок вычисления для выражений с участием пользовательских переменных не определен.
Таким образом, даже несмотря на то, что он работает в соответствии с вышеуказанным запросом, порядок оценки может все еще изменяться, например, когда вы добавляете условия или используете этот запрос в качестве представления или подзапроса в большем запросе. Это «особенность», которая будет удалена в будущем выпуске MySQL :
Предыдущие выпуски MySQL позволяли присваивать значение пользовательской переменной в операторах, отличных от SET
. Эта функциональность поддерживается в MySQL 8.0 для обратной совместимости, но подлежит удалению в будущем выпуске MySQL.
Как указано выше, начиная с MySQL 8.0, вы должны использовать рекурсивный with
синтаксис.
КПД
Для очень больших наборов данных это решение может стать медленным, поскольку find_in_set
операция не является наиболее идеальным способом поиска числа в списке, конечно же, не в списке, размер которого достигает такого же порядка, как число возвращаемых записей.
Альтернатива 1: with recursive
,connect by
Все больше и больше баз данных реализуют стандартный WITH [RECURSIVE]
синтаксис SQL: 1999 ISO для рекурсивных запросов (например, Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2 + , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperSQL 2.1.0+ , Teradata , MariaDB 10.2.2+ ). Начиная с версии 8.0, MySQL также поддерживает это . Смотрите верхнюю часть этого ответа для синтаксиса, чтобы использовать.
Некоторые базы данных имеют альтернативный нестандартный синтаксис для иерархического поиска, такой как CONNECT BY
предложение, доступное в Oracle , DB2 , Informix , CUBRID и других базах данных.
MySQL версии 5.7 не предлагает такую функцию. Когда ваш движок базы данных предоставляет этот синтаксис или вы можете перейти на тот, который это делает, тогда это, безусловно, лучший вариант. Если нет, то также рассмотрите следующие альтернативы.
Альтернатива 2: Идентификаторы стиля пути
Все станет намного проще, если вы назначите id
значения, которые содержат иерархическую информацию: путь. Например, в вашем случае это может выглядеть так:
ID | NAME
19 | category1
19/1 | category2
19/1/1 | category3
19/1/1/1 | category4
Тогда ваш select
будет выглядеть так:
select id,
name
from products
where id like '19/%'
Альтернатива 3: повторное самостоятельное соединение
Если вы знаете верхний предел того, насколько глубоким может стать ваше дерево иерархии, вы можете использовать стандартный sql
запрос, подобный следующему:
select p6.parent_id as parent6_id,
p5.parent_id as parent5_id,
p4.parent_id as parent4_id,
p3.parent_id as parent3_id,
p2.parent_id as parent2_id,
p1.parent_id as parent_id,
p1.id as product_id,
p1.name
from products p1
left join products p2 on p2.id = p1.parent_id
left join products p3 on p3.id = p2.parent_id
left join products p4 on p4.id = p3.parent_id
left join products p5 on p5.id = p4.parent_id
left join products p6 on p6.id = p5.parent_id
where 19 in (p1.parent_id,
p2.parent_id,
p3.parent_id,
p4.parent_id,
p5.parent_id,
p6.parent_id)
order by 1, 2, 3, 4, 5, 6, 7;
Смотрите эту скрипку
В where
состояние указывает , какой родитель вы хотите получить потомков. Вы можете расширить этот запрос с большим количеством уровней по мере необходимости.