una tabla recursivas recursiva jerarquica hacer ejemplos datos data crear consultas consulta como mysql sql recursive-query

mysql - recursivas - tabla recursiva en base de datos



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

Algo que no se menciona aquí, aunque un poco similar a la segunda alternativa de la respuesta aceptada pero diferente y de bajo costo para la consulta de gran jerarquía y los elementos fáciles (insertar eliminación de actualizaciones), agregaría 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 la ruta ORDER BY path utilizando la codificación base36 en lugar de la ID 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

Suprimir también el separador barra inclinada ''/'' mediante el uso de longitud fija y relleno a la identificación codificada

Explicación detallada de la 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 antepasados ​​retirados de un elemento

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 de MySQL a la que simplemente proporcione el id [por ejemplo, decir ''id = 19''], entonces debería obtener todos sus identificadores secundarios [es decir, el resultado debe tener los ids ''20, 21,22 '']. ... Además, se desconoce la jerarquía de los niños, puede variar ...

Además, ya tengo la solución usando el ciclo for ... Déjame saber cómo lograr lo mismo usando una sola consulta MySQL si es posible.


Consulta simple para listar el hijo de la primera recursión:

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 left join:

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 listar a 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


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

Estructura de la tabla

+-------------+----------------------+--------+ | 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 lo que se pretende con 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 está naturalmente representada 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 SELECCIONAR recursiva en Mysql?


El mejor enfoque con el que he surgido es

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

Lineage approach descr. se puede encontrar en cualquier lugar, por ejemplo Here o here . A partir de la función, that es lo que me fascinó.

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 tu solo

select get_lineage(the_id)

Espero que ayude a alguien :)


Es un poco complicado, comprueba si funciona para ti

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 nombre de tabla de manera apropiada.


He hecho una consulta para ti. Esto le dará 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 fiddle .



Lo encontré más fácil para:

1) crea una función que verificará si un elemento está en algún lugar de la jerarquía principal de otro. Algo así (no escribiré la función, hazlo 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));


Para las versiones de MySql que no son compatibles con las 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 fiddle .

El valor especificado en @pv := ''19'' debe establecerse en el id del padre del que desea seleccionar todos los descendientes de.

Esto funcionará también si un padre tiene varios hijos. Sin embargo, se requiere que cada registro cumpla con la condición parent_id < id , de lo contrario, los resultados no estarán completos.

Esta consulta utiliza una sintaxis MySql específica: las variables se asignan y modifican durante su ejecución. Se hacen algunas suposiciones sobre el orden de ejecución:

  • La cláusula from se evalúa primero. Entonces ahí es donde @pv se inicializa.
  • La cláusula where se evalúa para cada registro en el orden de recuperación de los alias de. Por lo tanto, aquí es donde se establece que una condición solo incluye registros para los cuales el padre ya estaba identificado como perteneciente al árbol descendiente (todos los descendientes del primario primario se agregan progresivamente a @pv ).
  • Las condiciones en esta cláusula where 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 el id a la lista principal, y esto solo debería ocurrir si el id pasa la primera condición. La función de length solo se llama para asegurarse de que esta condición sea siempre cierta, incluso si la cadena pv por algún motivo arrojara un valor falso.

Con todo, uno puede encontrar que estas suposiciones son demasiado arriesgadas como para confiar en ellas, no existe una garantía documentada para ellas y, aunque funciona de manera coherente, el orden de evaluación puede, en teoría, cambiar cuando utiliza esta consulta como una vista o sub - consulta en una consulta más grande.

También tenga en cuenta que para conjuntos de datos muy grandes, esta solución puede ser lenta, ya que la operación find_in_set 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 los registros devueltos.

Alternativa 1: WITH RECURSIVE , CONNECT BY

Cada vez más bases de datos implementan el SQL: estándar ISO 1999 WITH [RECURSIVE] sintaxis WITH [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 . Con esa sintaxis, la consulta se ve así:

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;

Algunas bases de datos tienen una sintaxis alternativa, no estándar para búsquedas jerárquicas, como la cláusula CONNECT BY disponible en bases de datos Oracle. DB2 también admite esta sintaxis alternativa.

MySql versión 5.7 no ofrece tal característica. Cuando su motor de base de datos proporciona esta sintaxis, esa es ciertamente la mejor opción. Si no, entonces también considere las siguientes alternativas.

Alternativa 2: Identificadores de estilo de camino

Las cosas se vuelven mucho más fáciles si se asignan valores de id que contienen la información jerárquica: una ruta. Por ejemplo, en tu caso esto podría verse así:

ID | NAME 19 | category1 19/1 | category2 19/1/1 | category3 19/1/1/1 | category4

Entonces su select se vería así:

select id, name from products where id like ''19/%''

Alternativa 3: Autocombinaciones repetidas

Si conoce un límite superior de cuán profundo puede llegar a ser el árbol jerárquico, puede usar un sql estándar como este:

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;

Mira este fiddle

La condición where especifica qué padre desea recuperar los descendientes de. Puede ampliar esta consulta con más niveles según sea necesario.


Prueba estos:

Definición de la 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 ;

Seleccione un 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 | +----+-----------+-----------------------------------------+

Filtrar 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 | +----+-----------+-----------------------------------------+


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

La otra forma de hacerlo es almacenar dos bits de datos adicionales, un valor izquierdo y derecho. El valor de la izquierda y la derecha se derivan de un recorrido de pre-pedido de la estructura de árbol que está representando.

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


Si necesita 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 tu ejemplo, la tabla de cierre se vería como

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 una gran desventaja cada vez que utiliza datos desnormalizados como este. Debe mantener la tabla de cierre junto con su tabla de categorías. La mejor forma es, probablemente, utilizar activadores, pero es algo complejo seguir correctamente las inserciones / actualizaciones / eliminaciones para las tablas de cierre. Al igual que con cualquier cosa, necesita ver sus requisitos y decidir qué enfoque es mejor para usted.

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


Solo use la clase de php de BlueM/tree para hacer el árbol de una tabla de auto-relación en mysql.

Tree y Tree / Node son clases de PHP para manejar datos que están estructurados jerárquicamente utilizando referencias padre de ID. Un ejemplo típico es una tabla en una base de datos relacional donde el campo "principal" 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 suministra los datos y Tree los usa, independientemente de dónde provengan 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); ...