Для 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, які не підтримують загальні вирази таблиць (до версії 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]
синтаксис ISO SQL: 1999 для рекурсивних запитів (наприклад, 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
стан вказує , який батько ви хочете отримати нащадків. Ви можете розширити цей запит на більше рівнів.