Para MySQL 8+: use la with
sintaxis recursiva .
Para MySQL 5.x: use variables en línea, ID de ruta o autouniones.
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 admiten expresiones de tabla comunes (hasta la versión 5.7), lo lograría 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 funcionará también si un padre tiene varios hijos. Sin embargo, se requiere que cada registro cumpla la condición parent_id < id
, de lo contrario los resultados no serán completos.
Asignaciones 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:
- La
from
cláusula se evalúa primero. Entonces ahí es donde @pv
se inicializa.
- La
where
cláusula se evalúa para cada registro en el orden de recuperación de los from
alias. Entonces, aquí es donde se establece una condición para incluir solo registros para los cuales el padre ya se identificó como parte del árbol descendente (todos los descendientes del padre primario se agregan progresivamente @pv
).
- Las condiciones en esta
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 la id
a la lista principal, y esto solo debería suceder si id
pasa la primera condición. La length
función sólo se llama para asegurarse de que esta condición se cumple siempre, incluso si la pv
cadena sería por alguna razón producir un valor Falsy.
En general, uno puede encontrar estas suposiciones demasiado arriesgadas para confiar en ellas. La documentación advierte:
puede obtener los resultados que espera, pero esto no está garantizado [...] el orden de evaluación para 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 puede cambiar, por ejemplo, cuando agrega condiciones o utiliza esta consulta como una vista o subconsulta en una consulta más grande. Es una "característica" que se eliminará en una futura versión de MySQL :
Las versiones anteriores de MySQL permitieron 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, desde MySQL 8.0 en adelante, debe usar la with
sintaxis recursiva .
Eficiencia
Para conjuntos de datos muy grandes, esta solución puede ser lenta, ya que la find_in_set
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 el número de registros devueltos.
Alternativa 1: with recursive
,connect by
Cada vez más bases de datos implementan la sintaxis estándar SQL: 1999 ISOWITH [RECURSIVE]
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 conocer la sintaxis que debe usar.
Algunas bases de datos tienen una sintaxis alternativa no estándar para búsquedas jerárquicas, como la CONNECT BY
cláusula disponible en Oracle , DB2 , Informix , CUBRID y otras bases de datos.
MySQL versión 5.7 no ofrece tal característica. Cuando su motor de base de datos proporciona esta sintaxis o puede migrar a una que sí lo haga, entonces esa es ciertamente la mejor opción. Si no, entonces también considere las siguientes alternativas.
Alternativa 2: identificadores de estilo de ruta
Las cosas se vuelven mucho más fáciles si asigna id
valores que contienen la información jerárquica: una ruta. Por ejemplo, en su caso esto podría verse así:
ID | NAME
19 | category1
19/1 | category2
19/1/1 | category3
19/1/1/1 | category4
Entonces tu select
se vería así:
select id,
name
from products
where id like '19/%'
Alternativa 3: autouniones repetidas
Si conoce un límite superior de cuán profundo puede llegar a ser su árbol de jerarquía, puede usar una sql
consulta estándar 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 violín
La where
condición especifica de qué padre desea recuperar los descendientes. Puede ampliar esta consulta con más niveles según sea necesario.