sql >> Base de Datos >  >> RDS >> Mysql

Prevención de la unión circular, búsquedas recursivas

Si utiliza MySQL 8.0 o MariaDB 10.2 (o superior) puede probar CTE recursivas (expresiones de tabla comunes) .

Asumiendo el siguiente esquema y datos:

CREATE TABLE `list_relation` (
  `child_id`  int unsigned NOT NULL,
  `parent_id` int unsigned NOT NULL,
  PRIMARY KEY (`child_id`,`parent_id`)
);
insert into list_relation (child_id, parent_id) values
    (2,1),
    (3,1),
    (4,2),
    (4,3),
    (5,3);

Ahora intenta insertar una nueva fila con child_id = 1 y parent_id = 4 . Pero eso crearía relaciones cíclicas (1->4->2->1 y 1->4->3->1 ), que desea evitar. Para averiguar si ya existe una relación inversa, puede usar la siguiente consulta, que mostrará todos los padres de la lista 4 (incluidos padres heredados/transitivos):

set @new_child_id  = 1;
set @new_parent_id = 4;

with recursive rcte as (
  select *
  from list_relation r
  where r.child_id = @new_parent_id
  union all
  select r.*
  from rcte
  join list_relation r on r.child_id = rcte.parent_id
)
select * from rcte

El resultado sería:

child_id | parent_id
       4 |         2
       4 |         3
       2 |         1
       3 |         1

Demostración

Puede ver en el resultado que la lista 1 es uno de los padres de lista 4 , y no insertaría el nuevo registro.

Ya que solo quiere saber si lista 1 está en el resultado, puede cambiar la última línea a

select * from rcte where parent_id = @new_child_id limit 1

o para

select exists (select * from rcte where parent_id = @new_child_id)

Por cierto:puede usar la misma consulta para evitar relaciones redundantes. Suponiendo que desea insertar el registro con child_id = 4 y parent_id = 1 . Esto sería redundante, ya que lista 4 ya hereda lista 1 sobre lista 2 y lista 3 . La siguiente consulta le mostraría que:

set @new_child_id  = 4;
set @new_parent_id = 1;

with recursive rcte as (
  select *
  from list_relation r
  where r.child_id = @new_child_id
  union all
  select r.*
  from rcte
  join list_relation r on r.child_id = rcte.parent_id
)
select exists (select * from rcte where parent_id = @new_parent_id)

Y puede usar una consulta similar para obtener todos los elementos heredados:

set @list = 4;

with recursive rcte (list_id) as (
  select @list
  union distinct
  select r.parent_id
  from rcte
  join list_relation r on r.child_id = rcte.list_id
)
select distinct i.*
from rcte
join item i on i.list_id = rcte.list_id