Mysql
 sql >> Database >  >> RDS >> Mysql

Impedire l'unione circolare, le ricerche ricorsive

Se usi MySQL 8.0 o MariaDB 10.2 (o superiore) puoi provare CTE ricorsive (espressioni di tabelle comuni) .

Assumendo lo schema e i dati seguenti:

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

Ora provi a inserire una nuova riga con child_id = 1 e parent_id = 4 . Ma ciò creerebbe relazioni cicliche (1->4->2->1 e 1->4->3->1 ), che si desidera prevenire. Per scoprire se esiste già una relazione inversa, puoi utilizzare la query seguente, che mostrerà tutti i genitori dell'elenco 4 (compresi i genitori ereditari/transitivi):

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

Il risultato sarebbe:

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

Demo

Puoi vedere nel risultato che l'elenco 1 è uno dei genitori della lista 4 e non inseriresti il ​​nuovo record.

Dal momento che vuoi solo sapere se elenco 1 è nel risultato, puoi modificare l'ultima riga in

select * from rcte where parent_id = @new_child_id limit 1

o a

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

A proposito:puoi usare la stessa query per prevenire relazioni ridondanti. Supponendo che tu voglia inserire il record con child_id = 4 e parent_id = 1 . Questo sarebbe ridondante, poiché elenco 4 eredita già lista 1 sopra elenco 2 e elenco 3 . La seguente query ti mostrerebbe che:

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 puoi utilizzare una query simile per ottenere tutti gli elementi ereditati:

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