una tipos subconsultas subconsulta resueltos recursividad hacer from ejercicios datos como anidadas sql oracle db2 hierarchical-query

sql - tipos - Detección de ciclos con factorización de subconsultas recursivas.



subconsultas sql ejercicios resueltos (6)

De la documentación en CONNECT_BY_ISCYCLE :

La pseudocolumna CONNECT_BY_ISCYCLE devuelve 1 si la fila actual tiene un hijo que también es su antecesor

y que en CYCLE :

Se considera que una fila forma un ciclo si una de sus filas de ancestro tiene los mismos valores para las columnas del ciclo.

En su ejemplo, la fila 2 tiene un hijo que también es su antecesor, pero su id no se ha devuelto todavía.

En otras palabras, CONNECT_BY_ISCYCLE comprueba los hijos (que aún no se han devuelto), mientras que CYCLE comprueba la fila actual (que ya se ha devuelto).

CONNECT BY se basa en filas, mientras que los CTE recursivos se basan en conjuntos.

No hay concepto de "niño" en un CTE recursivo. Es una operación basada en un conjunto que puede producir resultados completamente fuera del árbol. En términos generales, la parte de anclaje y la parte recursiva pueden incluso utilizar las diferentes tablas.

Dado que los CTE recursivos generalmente se usan para construir árboles de jerarquía, Oracle decidió agregar una verificación de ciclo. Pero debido a la manera en que los CTE recursivos funcionan de manera basada en conjuntos, generalmente es imposible saber si el siguiente paso generará un ciclo o no.

Para realizar el "siguiente" paso, todo el conjunto "actual" debe estar disponible, pero para generar cada fila del conjunto actual (que incluye la columna de ciclo) solo necesitamos tener los resultados de la siguiente operación. No es un problema con una sola fila (como en CONNECT BY ), pero es un problema con un conjunto como un todo.

Todavía no examiné Oracle 11 , pero SQL Server implementa los CTE recursivos simplemente ocultando un CONNECT BY detrás de ellos, lo que requiere colocar numerosas restricciones (todas las cuales prohíben efectivamente todas las operaciones basadas en conjuntos).

La implementación de PostgreSQL , por otro lado, está realmente basada en conjuntos.

Como se mencionó anteriormente, MySQL no implementa CTE en absoluto (no implementa HASH JOIN o MERGE JOIN también, solo los bucles anidados, así que no se sorprenda mucho).

Irónicamente, hoy recibí una carta sobre este tema, que cubriré en mi blog.

Actualizar:

Los CTE recursivos en SQL Server no son más que CONNECT BY disfrazados. Ver este artículo en mi blog para detalles impactantes:

Oracle SQL puede hacer consultas jerárquicas desde v2, usando su propia sintaxis CONNECT BY. En su última versión 11g 2, agregaron la factorización de subconsulta recursiva, también conocida como la cláusula recursiva con. Este es el estándar ANSI, y si lo entiendo correctamente, este también ha sido implementado por otros proveedores de RDBMS.

Al comparar la conexión con el recursivo, noté una diferencia en el conjunto de resultados al usar la detección de ciclos. La conexión por resultados es más intuitiva para mí, así que me pregunto si la implementación de Oracle contiene un error o si se trata de un ANSI estándar y el comportamiento esperado. Por lo tanto, mi pregunta es si puede verificar el recursivo con la consulta utilizando otras bases de datos como MySQL, DB2, SQL Server y otras. Siempre que las bases de datos sean compatibles con la cláusula recursiva, por supuesto.

Así es como funciona en Oracle 11.2.0.1.0

SQL> select * 2 from t 3 / ID PARENT_ID ---------- ---------- 1 2 2 1 2 rows selected.

La consulta utilizando la sintaxis de CONNECT BY:

SQL> select id 2 , parent_id 3 , connect_by_iscycle 4 from t 5 connect by nocycle parent_id = prior id 6 start with id = 1 7 / ID PARENT_ID CONNECT_BY_ISCYCLE ---------- ---------- ------------------ 1 2 0 2 1 1 2 rows selected.

Lo que me parece intuitivo. Sin embargo, usando la nueva sintaxis ANSI, devuelve una fila más:

SQL> with tr (id,parent_id) as 2 ( select id 3 , parent_id 4 from t 5 where id = 1 6 union all 7 select t.id 8 , t.parent_id 9 from t 10 join tr on t.parent_id = tr.id 11 ) cycle id set is_cycle to ''1'' default ''0'' 12 select id 13 , parent_id 14 , is_cycle 15 from tr 16 / ID PARENT_ID I ---------- ---------- - 1 2 0 2 1 0 1 2 1 3 rows selected.

Este es el script que puede usar para verificar:

create table t ( id number , parent_id number ); insert into t values (1, 2); insert into t values (2, 1); commit; with tr (id,parent_id) as ( select id , parent_id from t where id = 1 union all select t.id , t.parent_id from t join tr on t.parent_id = tr.id ) cycle id set is_cycle to ''1'' default ''0'' select id , parent_id , is_cycle from tr;


HASTA DONDE SE:

  • MySQL no soporta CTE recursivos
  • SQL Sever no admite la detección de ciclos en CTE recursivos

Los resultados de la conexión pueden no ser siempre intuitivos.

Las siguientes consultas muestran diferentes enfoques para detectar ciclos que comienzan con id = 3 para el gráfico en la imagen.

create table graph (id, id_parent) as (select 2, 1 from dual union all select 3, 1 from dual union all select 4, 3 from dual union all select 5, 4 from dual union all select 3, 5 from dual)

SQL> select level lvl, graph.*, connect_by_iscycle cycle 2 from graph 3 start with id = 3 4 connect by nocycle prior id = id_parent; LVL ID ID_PARENT CYCLE ---------- ---------- ---------- ---------- 1 3 1 0 2 4 3 0 3 5 4 1 1 3 5 0 2 4 3 0 3 5 4 1 6 rows selected. SQL> select level lvl, graph.*, connect_by_iscycle cycle 2 from graph 3 start with id = 3 4 connect by nocycle prior id = id_parent 5 and prior id_parent is not null; LVL ID ID_PARENT CYCLE ---------- ---------- ---------- ---------- 1 3 1 0 2 4 3 0 3 5 4 0 4 3 5 1 1 3 5 0 2 4 3 0 3 5 4 1 7 rows selected. SQL> with t(id, id_parent) as 2 (select * 3 from graph 4 where id = 3 5 union all 6 select g.id, g.id_parent 7 from t 8 join graph g 9 on t.id = g.id_parent) 10 search depth first by id set ord 11 cycle id set cycle to 1 default 0 12 select * from t; ID ID_PARENT ORD C ---------- ---------- ---------- - 3 1 1 0 4 3 2 0 5 4 3 0 3 5 4 1 3 5 5 0 4 3 6 0 5 4 7 0 3 5 8 1 8 rows selected.

El nodo con id = 3 tiene dos padres, por lo que Oracle atraviesa dos ciclos en este ejemplo.

(1, 3) -> (3, 4) -> (4, 5) -> (5, 3)

y

(5, 3) -> (3, 4) -> (4, 5)

Falta el borde (5, 3) del resultado de la primera consulta y el primer ciclo. Al mismo tiempo, el borde (5, 3) aparece en el resultado de la tercera consulta y el segundo ciclo dos veces.

¿Porque? Puede consultar la descripción de la lógica de detección de ciclo en la respuesta provista por Quassnoi. En inglés sencillo significa que

(1) connect by detecta un ciclo si la ID del niño para la fila actual es parte de las ID visitadas hasta ahora

(2) rec with detecta un ciclo si la ID de la fila actual es parte de las ID visitadas hasta ahora

El resultado de la segunda consulta parece el más natural, aunque hay un predicado adicional and prior id_parent is not null . En este caso

(3) detecta un ciclo si la ID de la fila actual es parte de las ID principales visitadas hasta ahora

Todas estas condiciones se implementan en las columnas cnt1, cnt2, cnt3 en la siguiente consulta.

SQL> with t(id, id_parent, path_id, path_id_parent, cnt1, cnt2, cnt3) as 2 (select g.*, 3 cast(''->'' || g.id as varchar2(4000)), 4 cast(''->'' || g.id_parent as varchar2(4000)), 5 0, 6 0, 7 0 8 from graph g 9 where id = 3 10 union all 11 select g.id, 12 g.id_parent, 13 t.path_id || ''->'' || g.id, 14 t.path_id_parent || ''->'' || g.id_parent, 15 regexp_count(t.path_id || ''->'', ''->'' || 16 (select id from graph c where c.id_parent = g.id) || ''->''), 17 regexp_count(t.path_id || ''->'', ''->'' || g.id || ''->''), 18 regexp_count(t.path_id_parent || ''->'', ''->'' || g.id || ''->'') 19 from t 20 join graph g 21 on t.id = g.id_parent 22 -- and t.cnt1 = 0 23 -- and t.cnt2 = 0 24 -- and t.cnt3 = 0 25 ) 26 search depth first by id set ord 27 cycle id set cycle to 1 default 0 28 select * from t; ID ID_PARENT PATH_ID PATH_ID_PARENT CNT1 CNT2 CNT3 ORD C ---------- ---------- --------------- --------------- ---- ---- ---- ---------- - 3 1 ->3 ->1 0 0 0 1 0 4 3 ->3->4 ->1->3 0 0 0 2 0 5 4 ->3->4->5 ->1->3->4 1 0 0 3 0 3 5 ->3->4->5->3 ->1->3->4->5 1 1 1 4 1 3 5 ->3 ->5 0 0 0 5 0 4 3 ->3->4 ->5->3 0 0 0 6 0 5 4 ->3->4->5 ->5->3->4 1 0 1 7 0 3 5 ->3->4->5->3 ->5->3->4->5 1 1 1 8 1 8 rows selected.

Si elimina los comentarios de cnt1 / cnt2 / cnt3 y elimina "cycle id set cycle a 1 default 0", la consulta devolverá el resultado como la consulta correspondiente arriba. En otras palabras , puede evitar la cycle clause e implementar cualquier lógica de detección de ciclo que encuentre más intuitiva .

Se pueden encontrar detalles adicionales sobre las jerarquías de desplazamiento y la detección de ciclos en el libro Oracle SQL Revealed .


MySQL Server versión 5.0.45 no me gustó with :

ERROR 1064 (42000): Tiene un error en su sintaxis SQL; revise el manual que corresponde a la versión de su servidor MySQL para conocer la sintaxis correcta para usar near ''with tr (id, parent_id) as (seleccione id, parent_id de t donde id = 1 union all s'' en la línea 1.


PostgreSQL admite consultas jerárquicas de estilo CON, pero no tiene detección automática de ciclos. Esto significa que debe escribir el suyo propio y el número de filas devueltas depende de la forma en que especifique las condiciones de unión en la parte recursiva de la consulta.

Ambos ejemplos usan una matriz si los ID (llamados all_ids) para detectar bucles:

WITH recursive tr (id, parent_id, all_ids, cycle) AS ( SELECT id, parent_id, ARRAY[id], false FROM t WHERE id = 1 UNION ALL SELECT t.id, t.parent_id, all_ids || t.id, t.id = ANY(all_ids) FROM t JOIN tr ON t.parent_id = tr.id AND NOT cycle) SELECT id, parent_id, cycle FROM tr; id | parent_id | cycle ----+-----------+------- 1 | 2 | f 2 | 1 | f 1 | 2 | t WITH recursive tr (id, parent_id, all_ids, cycle) AS ( SELECT id, parent_id, ARRAY[id], false FROM t WHERE id = 1 UNION ALL SELECT t.id, t.parent_id, all_ids || t.id, (EXISTS(SELECT 1 FROM t AS x WHERE x.id = t.parent_id)) FROM t JOIN tr ON t.parent_id = tr.id WHERE NOT t.id = ANY(all_ids)) SELECT id, parent_id, cycle FROM tr; id | parent_id | cycle ----+-----------+------- 1 | 2 | f 2 | 1 | t


WITH RECURSIVE s (master, slave, all_ids, cycle) AS ( SELECT master, slave, ARRAY[master], false FROM binding WHERE master=3477 UNION ALL SELECT d.master, d.slave, all_ids || d.master, d.slave = ANY(all_ids) FROM binding AS d JOIN s ON (d.master = s.slave) WHERE NOT d.master = ANY(all_ids) ) SELECT * FROM s;

Creo que mejor es esta condición d.slave = ANY(all_ids)