Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Impedindo a junção circular, pesquisas recursivas


Se você usa MySQL 8.0 ou MariaDB 10.2 (ou superior) você pode tentar CTEs recursivas (expressões de tabela comuns) .

Assumindo o seguinte esquema e dados:
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);

Agora você tenta inserir uma nova linha com child_id = 1 e parent_id = 4 . Mas isso criaria relações cíclicas (1->4->2->1 e 1->4->3->1 ), que você deseja evitar. Para descobrir se já existe uma relação inversa, você pode usar a seguinte consulta, que mostrará todos os pais da lista 4 (incluindo pais herdados/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

O resultado seria:
child_id | parent_id
       4 |         2
       4 |         3
       2 |         1
       3 |         1

Demonstração

Você pode ver no resultado, que a lista 1 é um dos pais da lista 4 , e você não inseriria o novo registro.

Como você só quer saber se a lista 1 está no resultado, você pode alterar a última linha para
select * from rcte where parent_id = @new_child_id limit 1

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

BTW:Você pode usar a mesma consulta para evitar relações redundantes. Supondo que você queira inserir o registro com child_id = 4 e parent_id = 1 . Isso seria redundante, pois a lista 4 já herda lista 1 sobre a lista 2 e lista 3 . A consulta a seguir mostraria 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)

E você pode usar uma consulta semelhante para obter todos os itens herdados:
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