sql >> Database >  >> RDS >> Mysql

Circulair samenvoegen voorkomen, recursief zoeken

Als u MySQL 8.0 gebruikt of MariaDB 10.2 (of hoger) kunt u recursieve CTE's (algemene tabeluitdrukkingen) proberen .

Uitgaande van het volgende schema en de volgende gegevens:

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);

Nu probeer je een nieuwe rij in te voegen met child_id = 1 en parent_id = 4 . Maar dat zou cyclische relaties creëren (1->4->2->1 en 1->4->3->1 ), die u wilt voorkomen. Om erachter te komen of er al een omgekeerde relatie bestaat, kun je de volgende zoekopdracht gebruiken, die alle bovenliggende elementen van lijst 4 toont (inclusief geërfde/transitieve ouders):

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

Het resultaat zou zijn:

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

Demo

U kunt in het resultaat zien dat de lijst 1 is een van de ouders van lijst 4 , en je zou het nieuwe record niet invoegen.

Omdat je alleen wilt weten of lijst 1 in het resultaat staat, kunt u de laatste regel wijzigen in

select * from rcte where parent_id = @new_child_id limit 1

of naar

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

BTW:U kunt dezelfde query gebruiken om overbodige relaties te voorkomen. Ervan uitgaande dat u het record wilt invoegen met child_id = 4 en parent_id = 1 . Dit zou overbodig zijn, aangezien lijst 4 erft al lijst 1 over lijst 2 en lijst 3 . De volgende zoekopdracht zou u dat laten zien:

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)

En u kunt een vergelijkbare zoekopdracht gebruiken om alle overgenomen items te krijgen:

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


  1. MySQL JOIN met LIMIT 1 op samengevoegde tafel

  2. PostgreSQL configureren voor observeerbaarheid

  3. Hoe kan ik de naam van een kolom in laravel wijzigen met behulp van migratie?

  4. Importeer meerdere CSV-bestanden naar SQL Server vanuit een map