Mysql
 sql >> Base de données >  >> RDS >> Mysql

Empêcher les jointures circulaires, les recherches récursives

Si vous utilisez MySQL 8.0 ou MariaDB 10.2 (ou supérieur) vous pouvez essayer les CTE récursifs (expressions de table communes) .

En supposant le schéma et les données suivants :

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

Maintenant, vous essayez d'insérer une nouvelle ligne avec child_id = 1 et parent_id = 4 . Mais cela créerait des relations cycliques (1->4->2->1 et 1->4->3->1 ), que vous souhaitez empêcher. Pour savoir si une relation inverse existe déjà, vous pouvez utiliser la requête suivante, qui affichera tous les parents de la liste 4 (y compris les parents hérités/transitifs) :

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

Le résultat serait :

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

Démo

Vous pouvez voir dans le résultat, que la liste 1 est l'un des parents de liste 4 , et vous n'inséreriez pas le nouvel enregistrement.

Puisque vous voulez seulement savoir si liste 1 est dans le résultat, vous pouvez remplacer la dernière ligne par

select * from rcte where parent_id = @new_child_id limit 1

ou à

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

BTW :Vous pouvez utiliser la même requête pour éviter les relations redondantes. En supposant que vous souhaitiez insérer l'enregistrement avec child_id = 4 et parent_id = 1 . Ce serait redondant, puisque liste 4 hérite déjà de la liste 1 sur liste 2 et liste 3 . La requête suivante vous montrerait 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)

Et vous pouvez utiliser une requête similaire pour obtenir tous les éléments hérités :

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