Para MySQL 8+: use el recursivo with
sintaxis.
Para MySQL 5.x: use variables en línea, ID de ruta o autocombinaciones.
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;
El valor especificado en parent_id = 19
debe establecerse en el id
del padre del que desea seleccionar todos los descendientes.
MySQL 5.x
Para las versiones de MySQL que no son compatibles con Common Table Expressions (hasta la versión 5.7), podría lograr esto con la siguiente consulta:
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))
Aquí hay un violín .
Aquí, el valor especificado en @pv := '19'
debe establecerse en el id
del padre del que desea seleccionar todos los descendientes.
Esto también funcionará si un padre tiene múltiples niños. Sin embargo, se requiere que cada registro cumpla la condición parent_id < id
, de lo contrario los resultados no estarán completos.
Asignaciones de variables dentro de una consulta
Esta consulta utiliza una sintaxis específica de MySQL:las variables se asignan y modifican durante su ejecución. Se hacen algunas suposiciones sobre el orden de ejecución:
- El
from
la cláusula se evalúa primero. Así que ahí es donde@pv
se inicializa. - El
where
La cláusula se evalúa para cada registro en el orden de recuperación defrom
alias. Entonces, aquí es donde se establece una condición para incluir solo registros para los cuales el padre ya estaba identificado como parte del árbol de descendientes (todos los descendientes del padre principal se agregan progresivamente a@pv
). - Las condiciones en este
where
cláusula se evalúan en orden, y la evaluación se interrumpe una vez que el resultado total es seguro. Por lo tanto, la segunda condición debe estar en segundo lugar, ya que agrega elid
a la lista principal, y esto solo debería suceder si elid
pasa la primera condición. Lalength
La función solo se llama para asegurarse de que esta condición sea siempre cierta, incluso sipv
por alguna razón, la cadena produciría un valor falso.
En general, uno puede encontrar estas suposiciones demasiado arriesgadas para confiar en ellas. La documentación advierte:
puede obtener los resultados esperados, pero esto no está garantizado [...] el orden de evaluación de las expresiones que involucran variables de usuario no está definido.
Entonces, aunque funciona de manera consistente con la consulta anterior, el orden de evaluación aún puede cambiar, por ejemplo, cuando agrega condiciones o usa esta consulta como una vista o subconsulta en una consulta más grande. Es una "característica" que se eliminará en un futuro Versión de MySQL :
Las versiones anteriores de MySQL hicieron posible asignar un valor a una variable de usuario en declaraciones distintas de SET
. Esta funcionalidad es compatible con MySQL 8.0 por compatibilidad con versiones anteriores, pero está sujeta a eliminación en una versión futura de MySQL.
Como se indicó anteriormente, a partir de MySQL 8.0 en adelante, debe usar el recursivo with
sintaxis.
Eficiencia
Para conjuntos de datos muy grandes, esta solución puede volverse lenta, ya que find_in_set
La operación no es la forma más ideal de encontrar un número en una lista, ciertamente no en una lista que alcanza un tamaño en el mismo orden de magnitud que la cantidad de registros devueltos.
Alternativa 1:with recursive
, connect by
Cada vez más bases de datos implementan el SQL:1999 ISO standard WITH [RECURSIVE]
sintaxis
para consultas recursivas (por ejemplo, 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+
). Y a partir de la versión 8.0, también MySQL lo admite
. Consulte la parte superior de esta respuesta para ver la sintaxis que debe usar.
Algunas bases de datos tienen una sintaxis alternativa no estándar para búsquedas jerárquicas, como CONNECT BY
cláusula disponible en Oracle
, DB2
, Informix , CUBRID
y otras bases de datos.
MySQL versión 5.7 no ofrece esa función. Cuando su motor de base de datos proporciona esta sintaxis o puede migrar a uno que lo haga, entonces esa es sin duda la mejor opción para elegir. Si no es así, considere también las siguientes alternativas.
Alternativa 2:Identificadores de estilo de ruta
Las cosas se vuelven mucho más fáciles si asignas id
valores que contienen la información jerárquica:una ruta. Por ejemplo, en su caso, esto podría verse así:
ID | NOMBRE |
---|---|
19 | categoría1 |
19/1 | categoría2 |
19/1/1 | categoría3 |
19/1/1/1 | categoría4 |
Entonces tu select
se vería así:
select id,
name
from products
where id like '19/%'
Alternativa 3:Auto-uniones repetidas
Si conoce un límite superior de cuán profundo puede llegar a ser su árbol de jerarquía, puede usar un sql
estándar consulta como esta:
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;
Ver este fiddle
El where
condition especifica de qué padre desea recuperar los descendientes. Puede ampliar esta consulta con más niveles según sea necesario.