Cómo crear una consulta recursiva jerárquica de MySQL

271

Tengo una tabla MySQL que es la siguiente:

id | name        | parent_id
19 | category1   | 0
20 | category2   | 19
21 | category3   | 20
22 | category4   | 21
......

Ahora, quiero tener una única consulta MySQL a la que simplemente proporcione la identificación [por ejemplo, decir 'id = 19'], entonces debería obtener todos sus identificadores secundarios [es decir, el resultado debería tener identificadores '20, 21,22 ']. ... Además, no se conoce la jerarquía de los hijos, puede variar ...

Además, ya tengo la solución usando el bucle for ..... Si es posible, avíseme cómo lograrlo usando una sola consulta MySQL.

Tarun Parswani
fuente
Supongamos que la jerarquía tiene 7 niveles de profundidad. ¿Cómo espera que se vea la tabla de salida?
Jonathan Leffler
1
MySQL (todavía) no admite consultas jerárquicas (como lo hacen otros DBMS modernos). Deberá escribir un procedimiento almacenado o utilizar un modelo de datos diferente.
a_horse_with_no_name
1
MYSQL 8.0 admitirá consultas recursivas usando CTE (expresiones de tabla comunes)
user3712320
¿Qué hay de obtener la lista completa de publicaciones a partir de la última identificación de comentario? ¿O el último niño?
Joe

Respuestas:

393

Para MySQL 8+: use la withsintaxis 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 = 19debe establecerse en el iddel 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 iddel 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 fromcláusula se evalúa primero. Entonces ahí es donde @pvse inicializa.
  • La wherecláusula se evalúa para cada registro en el orden de recuperación de los fromalias. 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 whereclá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 ida la lista principal, y esto solo debería suceder si idpasa la primera condición. La lengthfunción sólo se llama para asegurarse de que esta condición se cumple siempre, incluso si la pvcadena 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 withsintaxis recursiva .

Eficiencia

Para conjuntos de datos muy grandes, esta solución puede ser lenta, ya que la find_in_setoperació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 BYclá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 idvalores 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 selectse 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 sqlconsulta 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 wherecondición especifica de qué padre desea recuperar los descendientes. Puede ampliar esta consulta con más niveles según sea necesario.

trincot
fuente
26
Me gusta tu explicación No solo da una respuesta, explica por qué resuelve el problema para que podamos aprender de él. EDITAR: también es genial que no se base en conocer el número de niveles de antemano.
Byson
1
@Bison, agradezco mucho tu opinión. ¡Gracias!
Trincot
2
@ Avión, no es algo que tengas que poner en alguna parte, es un requisito que para todos los registros esta condición sea verdadera. Si tiene uno o más registros donde parent_id > idno puede usar esta solución.
trincot
2
Para cualquiera que quiera usar el WITH RECURSIVEmétodo, el siguiente artículo me pareció realmente útil con diferentes escenarios, como profundidad de recursión, distinciones y ciclos de detección y cierre
Horse
2
Probé la solución principal en MySQL5.7 en mi computadora, en mis propias tablas, pero eso no funcionó debido al equivalente de la cláusula @pv: = concat (@pv, ',', id) se evalúa como falso. Lo arreglé cambiándolo a la longitud (@pv: = concat (@pv, ',', id))> 0, por lo que siempre es cierto.
KC Wong
82

Desde el blog Gestión de datos jerárquicos en MySQL

Estructura de la mesa

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+

Consulta:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

Salida

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+

La mayoría de los usuarios en un momento u otro han tratado con datos jerárquicos en una base de datos SQL y, sin duda, aprendieron que la gestión de datos jerárquicos no es para lo que está destinada una base de datos relacional. Las tablas de una base de datos relacional no son jerárquicas (como XML), sino que son simplemente una lista plana. Los datos jerárquicos tienen una relación padre-hijo que no se representa naturalmente en una tabla de base de datos relacional. Lee mas

Consulte el blog para más detalles.

EDITAR:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv

Salida:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21

Referencia: ¿Cómo hacer la consulta SELECCIÓN recursiva en Mysql?

Damodaran
fuente
23
Eso está bien siempre que no haya más de 4 niveles como máximo en la jerarquía. Si hay N niveles, debe saberlo para crear la consulta correctamente.
Jonathan Leffler el
2
@Damodaran, gracias por su respuesta ... Lo que necesitaba es una condición en la que no se conoce el número de niños ... y en el blog que usa un concepto de unión interna en el sentido de que se requiere conocer la jerarquía, lo cual no se conoce en mi caso ... así que hágame saber su punto de vista sobre el mismo ... Entonces, en palabras simples, necesito una consulta para manejar 'n' niveles de contratación donde 'n' no se conoce .....
Tarun Parswani
1
@ user3036105: no es posible hacer esto en MySQL con una sola consulta SQL. MySQL simplemente no es lo suficientemente avanzado para eso. Si realmente necesita esto, considere actualizar a un DBMS que admita consultas recursivas.
a_horse_with_no_name
55
> La mayoría de los usuarios en un momento u otro han tratado con datos jerárquicos en una base de datos SQL y, sin duda, han aprendido que la gestión de datos jerárquicos no es para lo que está destinada una base de datos relacional. Quizás te referías a una base de datos MySQL. Una base de datos Oracle maneja bastante bien los datos jerárquicos y las consultas.
Peter Nosko
1
"... la gestión de datos jerárquicos no es para lo que está destinada una base de datos relacional ..." Si bien esta puede no haber sido la intención original de una base de datos relacional, en el mundo real los datos jerárquicos son increíblemente comunes y MySQL debería reflejar cómo las personas realmente necesitan usar sus datos en escenarios del mundo real.
Dave L
9

Prueba estos:

Definición de tabla:

DROP TABLE IF EXISTS category;
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20),
    parent_id INT,
    CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
    REFERENCES category (id)
) engine=innodb;

Filas experimentales:

INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);

Procedimiento almacenado recursivo:

DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
    DECLARE catname VARCHAR(20);
    DECLARE temppath TEXT;
    DECLARE tempparent INT;
    SET max_sp_recursion_depth = 255;
    SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
    IF tempparent IS NULL
    THEN
        SET path = catname;
    ELSE
        CALL getpath(tempparent, temppath);
        SET path = CONCAT(temppath, '/', catname);
    END IF;
END$$
DELIMITER ;

Función de envoltura para el procedimiento almacenado:

DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getpath(cat_id, res);
    RETURN res;
END$$
DELIMITER ;

Seleccionar ejemplo:

SELECT id, name, getpath(id) AS path FROM category;

Salida:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+

Filtrando filas con cierta ruta:

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

Salida:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+
Fandi Susanto
fuente
1
Esto no funcionará para más de un niño. ej .(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
código limpio el
3
Estoy bastante seguro de que funciona para más de un niño. Incluso lo probé de nuevo.
Fandi Susanto
@Fandi Susanto, gracias, mucho me ayuda.
Dogan Ozer
9

El mejor enfoque que se me ocurrió es

  1. Use linaje para almacenar \ sort \ trace árboles. Eso es más que suficiente, y funciona miles de veces más rápido para leer que cualquier otro enfoque. También permite permanecer en ese patrón incluso si DB cambiará (ya que CUALQUIER db permitirá que se use ese patrón)
  2. Utilice la función que determina el linaje para una identificación específica.
  3. Úselo como lo desee (en selectos, o en operaciones CUD, o incluso por trabajos).

Enfoque de linaje descr. se puede encontrar donde sea, por ejemplo aquí o aquí . En cuanto a la función, eso es lo que me inspiró.

Al final, obtuve una solución más o menos simple, relativamente rápida y SIMPLE.

Cuerpo de la función

-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$

CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
    READS SQL DATA
BEGIN

 DECLARE v_rec INT DEFAULT 0;

 DECLARE done INT DEFAULT FALSE;
 DECLARE v_res text DEFAULT '';
 DECLARE v_papa int;
 DECLARE v_papa_papa int DEFAULT -1;
 DECLARE csr CURSOR FOR 
  select _id,parent_id -- @n:=@n+1 as rownum,T1.* 
  from 
    (SELECT @r AS _id,
        (SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := the_id, @l := 0,@n:=0) vars,
        table m
    WHERE @r <> 0
    ) T1
    where T1.parent_id is not null
 ORDER BY T1.lvl DESC;
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    open csr;
    read_loop: LOOP
    fetch csr into v_papa,v_papa_papa;
        SET v_rec = v_rec+1;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- add first
        IF v_rec = 1 THEN
            SET v_res = v_papa_papa;
        END IF;
        SET v_res = CONCAT(v_res,'-',v_papa);
    END LOOP;
    close csr;
    return v_res;
END

Y luego solo

select get_lineage(the_id)

Espero que ayude a alguien :)

Der Zinger
fuente
9

Hice lo mismo para otra consulta aquí

Mysql select recursive get all child with multiple level

La consulta será:

SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
  SELECT @pv:=(
    SELECT GROUP_CONCAT(id SEPARATOR ',')
    FROM table WHERE parent_id IN (@pv)
  ) AS lv FROM table 
  JOIN
  (SELECT @pv:=1)tmp
  WHERE parent_id IN (@pv)
) a;
Dheerendra Kulkarni
fuente
¿Cómo podemos hacer esto? SELECT idFolder, (SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM ( SELECT @pv:=(SELECT GROUP_CONCAT(idFolder SEPARATOR ',') FROM Folder WHERE idFolderParent IN (@pv)) AS lv FROM Folder JOIN (SELECT @pv:= F1.idFolder )tmp WHERE idFolderParent IN (@pv)) a) from folder F1 where id > 10; No puedo recomendar F1.idFolder para @pv
Rahul
Volví a crear la tabla de la pregunta original de OP con los datos como se muestra en su comentario, luego ejecuté su consulta aquí y obtuve una sola NULLcomo resultado. ¿Sabes por qué podría ser eso? ¿Existen requisitos previos en términos del motor de la base de datos, o ha cambiado algo desde que hizo esta respuesta que hace que esta consulta esté desactualizada?
Digital Ninja
7

Si necesita una velocidad de lectura rápida, la mejor opción es usar una tabla de cierre. Una tabla de cierre contiene una fila para cada par ancestro / descendiente. Entonces, en su ejemplo, la tabla de cierre se vería así

ancestor | descendant | depth
0        | 0          | 0
0        | 19         | 1
0        | 20         | 2
0        | 21         | 3
0        | 22         | 4
19       | 19         | 0
19       | 20         | 1
19       | 21         | 3
19       | 22         | 4
20       | 20         | 0
20       | 21         | 1
20       | 22         | 2
21       | 21         | 0
21       | 22         | 1
22       | 22         | 0

Una vez que tenga esta tabla, las consultas jerárquicas se vuelven muy fáciles y rápidas. Para obtener todos los descendientes de la categoría 20:

SELECT cat.* FROM categories_closure AS cl
INNER JOIN categories AS cat ON cat.id = cl.descendant
WHERE cl.ancestor = 20 AND cl.depth > 0

Por supuesto, hay un gran inconveniente cada vez que utiliza datos desnormalizados como este. Debe mantener la tabla de cierre junto con su tabla de categorías. Probablemente, la mejor manera es usar disparadores, pero es algo complejo rastrear correctamente las inserciones / actualizaciones / eliminaciones para las tablas de cierre. Como con cualquier cosa, debe analizar sus requisitos y decidir qué enfoque es mejor para usted.

Editar : vea la pregunta ¿Cuáles son las opciones para almacenar datos jerárquicos en una base de datos relacional? Para más opciones. Existen diferentes soluciones óptimas para diferentes situaciones.

Justin Howard
fuente
4

Consulta simple para enumerar la primera recursión del niño:

select @pv:=id as id, name, parent_id
from products
join (select @pv:=19)tmp
where parent_id=@pv

Resultado:

id  name        parent_id
20  category2   19
21  category3   20
22  category4   21
26  category24  22

... con unión izquierda:

select
    @pv:=p1.id as id
  , p2.name as parent_name
  , p1.name name
  , p1.parent_id
from products p1
join (select @pv:=19)tmp
left join products p2 on p2.id=p1.parent_id -- optional join to get parent name
where p1.parent_id=@pv

La solución de @tincot para enumerar todos los niños:

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) > 0
and     @pv := concat(@pv, ',', id)

Pruébelo en línea con Sql Fiddle y vea todos los resultados.

http://sqlfiddle.com/#!9/a318e3/4/0

lynx_74
fuente
3

Puede hacerlo así en otras bases de datos con bastante facilidad con una consulta recursiva (YMMV sobre rendimiento).

La otra forma de hacerlo es almacenar dos bits adicionales de datos, un valor izquierdo y derecho. Los valores izquierdo y derecho se derivan de un recorrido de preorden de la estructura de árbol que está representando.

Esto se conoce como Recorrido de árbol de pedido anticipado modificado y le permite ejecutar una consulta simple para obtener todos los valores principales a la vez. También se conoce con el nombre de "conjunto anidado".

Phil John
fuente
Quería agregar un comentario similar al tuyo, pero como lo hiciste, agregaré
Miroslaw Opoka
2

Simplemente use BlueM / tree php class para hacer un árbol de una tabla de autorrelación en mysql

Tree y Tree \ Node son clases PHP para manejar datos estructurados jerárquicamente utilizando referencias de ID padre. Un ejemplo típico es una tabla en una base de datos relacional donde el campo "padre" de cada registro hace referencia a la clave primaria de otro registro. Por supuesto, Tree no solo puede usar datos que se originan en una base de datos, sino cualquier cosa: usted proporciona los datos, y Tree los usa, independientemente de dónde provienen los datos y cómo se procesaron. Lee mas

Aquí hay un ejemplo del uso de BlueM / tree:

<?php 
require '/path/to/vendor/autoload.php'; $db = new PDO(...); // Set up your database connection 
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title'); 
$records = $stm->fetchAll(PDO::FETCH_ASSOC); 
$tree = new BlueM\Tree($records); 
...
Saleh Mosleh
fuente
2

ingrese la descripción de la imagen aquí

Es una tabla de categorías .

SELECT  id,
        NAME,
        parent_category 
FROM    (SELECT * FROM category
         ORDER BY parent_category, id) products_sorted,
        (SELECT @pv := '2') initialisation
WHERE   FIND_IN_SET(parent_category, @pv) > 0
AND     @pv := CONCAT(@pv, ',', id)

Salida:: ingrese la descripción de la imagen aquí

Pradip Rupareliya
fuente
1
¿Puede explicar esto? Pero te garantizo que esto está funcionando. Gracias.
wobsoriano
1
por favor explique la consulta y cuál es el significado de @pv ?? ¿Cómo funciona el bucle en esta consulta?
Amanjot Kaur
2
No parece funcionar en todos los niveles si hay niños que tienen identificaciones más bajas que sus padres. :(
Jonas
1
@Jonas me tomó 20 minutos para identificar el problema real, intentando con una combinación diferente. sí tienes razón. No funcionará con una ID inferior a su ID principal. ¿Tienes alguna solución?
muaaz
@muaaz Finalmente lo resolví usando un campo "ruta" que contiene la ruta para la fila respectiva, por ejemplo, la fila con ID 577 tiene ruta "/ 1/2/45/577 /". Si está buscando todos los elementos secundarios de ID 2, simplemente puede seleccionar todas las filas con la ruta LIKE "/ 1/2 /%". El único inconveniente es que debe actualizar las rutas en sus métodos de actualización. Pero para MySQL 5.6 (compatible), fue la única solución que funcionó para mí.
Jonas
1

Es un poco complicado, mira esto si te funciona

select a.id,if(a.parent = 0,@varw:=concat(a.id,','),@varw:=concat(a.id,',',@varw)) as list from (select * from recursivejoin order by if(parent=0,id,parent) asc) a left join recursivejoin b on (a.id = b.parent),(select @varw:='') as c  having list like '%19,%';

Enlace de violín de SQL http://www.sqlfiddle.com/#!2/e3cdf/2

Reemplace con su campo y el nombre de la tabla adecuadamente.

senK
fuente
En este caso , no funcionará sqlfiddle.com/#!2/19360/2 , con este truco, al menos debe ordenar primero por nivel jerárquico.
Jaugar Chang
1

Algo no mencionado aquí, aunque un poco similar a la segunda alternativa de la respuesta aceptada pero diferente y de bajo costo para consultas de gran jerarquía y elementos fáciles (insertar actualización eliminar), estaría agregando una columna de ruta persistente para cada elemento.

algo como:

id | name        | path
19 | category1   | /19
20 | category2   | /19/20
21 | category3   | /19/20/21
22 | category4   | /19/20/21/22

Ejemplo:

-- get children of category3:
SELECT * FROM my_table WHERE path LIKE '/19/20/21%'
-- Reparent an item:
UPDATE my_table SET path = REPLACE(path, '/19/20', '/15/16') WHERE path LIKE '/19/20/%'

Optimice la longitud de la ruta y ORDER BY pathutilice la codificación base36 en lugar de la identificación de ruta numérica real

 // base10 => base36
 '1' => '1',
 '10' => 'A',
 '100' => '2S',
 '1000' => 'RS',
 '10000' => '7PS',
 '100000' => '255S',
 '1000000' => 'LFLS',
 '1000000000' => 'GJDGXS',
 '1000000000000' => 'CRE66I9S'

https://en.wikipedia.org/wiki/Base36

Suprime también el separador de barra '/' usando longitud fija y relleno para la identificación codificada

Explicación detallada de optimización aquí: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

QUE HACER

construir una función o procedimiento para dividir el camino para los antepasados ​​retreivos de un elemento

MTK
fuente
¡Gracias! Interesante conbase36
Vlad
0

Esto funciona para mí, espero que esto también funcione para ti. Le dará un conjunto de registros Root a Child para cualquier menú específico. Cambie el nombre del campo según sus requisitos.

SET @id:= '22';

SELECT Menu_Name, (@id:=Sub_Menu_ID ) as Sub_Menu_ID, Menu_ID 
FROM 
    ( SELECT Menu_ID, Menu_Name, Sub_Menu_ID 
      FROM menu 
      ORDER BY Sub_Menu_ID DESC
    ) AS aux_table 
    WHERE Menu_ID = @id
     ORDER BY Sub_Menu_ID;
Monzur
fuente
No parece funcionar en todos los niveles si hay niños que tienen identificaciones mayores que sus padres
muaaz
-1

Me resultó más fácil:

1) cree una función que verifique si un elemento está en algún lugar de la jerarquía principal de otro. Algo como esto (no escribiré la función, hágala con WHILE DO):

is_related(id, parent_id);

en tu ejemplo

is_related(21, 19) == 1;
is_related(20, 19) == 1;
is_related(21, 18) == 0;

2) use una sub-selección, algo como esto:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));
cripox
fuente
-1

He hecho una consulta por ti. Esto le dará una categoría recursiva con una sola consulta:

SELECT id,NAME,'' AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 WHERE prent is NULL
UNION 
SELECT b.id,a.name,b.name AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id WHERE a.prent is NULL AND b.name IS NOT NULL 
UNION 
SELECT c.id,a.name,b.name AS subName,c.name AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id WHERE a.prent is NULL AND c.name IS NOT NULL 
UNION 
SELECT d.id,a.name,b.name AS subName,c.name AS subsubName,d.name AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id LEFT JOIN Table1 AS d ON d.prent=c.id WHERE a.prent is NULL AND d.name IS NOT NULL 
ORDER BY NAME,subName,subsubName,subsubsubName

Aquí hay un violín .

Manish
fuente
Elimina / edita tu respuesta para recuperar tu reputación positiva.
fWd82