Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Zirkuläres Verbinden, rekursive Suchen verhindern

Wenn Sie MySQL 8.0 verwenden oder MariaDB 10.2 (oder höher) können Sie rekursive CTEs (gemeinsame Tabellenausdrücke) ausprobieren .

Unter der Annahme des folgenden Schemas und der folgenden Daten:

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

Nun versuchen Sie eine neue Zeile mit child_id = 1 einzufügen und parent_id = 4 . Aber das würde zyklische Beziehungen erzeugen (1->4->2->1 und 1->4->3->1 ), die Sie verhindern möchten. Um herauszufinden, ob bereits eine umgekehrte Beziehung besteht, können Sie die folgende Abfrage verwenden, die alle Eltern von Liste 4 anzeigt (einschließlich geerbter/transitiver Eltern):

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

Das Ergebnis wäre:

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

Demo

Im Ergebnis sehen Sie, dass die Liste 1 ist einer der Eltern von Liste 4 , und Sie würden den neuen Datensatz nicht einfügen.

Da Sie nur wissen wollen, ob Liste 1 im Ergebnis steht, können Sie die letzte Zeile in

ändern
select * from rcte where parent_id = @new_child_id limit 1

oder zu

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

Übrigens:Sie können dieselbe Abfrage verwenden, um redundante Beziehungen zu verhindern. Angenommen, Sie möchten den Datensatz mit child_id = 4 einfügen und parent_id = 1 . Dies wäre überflüssig, da Liste 4 erbt bereits Liste 1 über Liste 2 und Liste 3 . Die folgende Abfrage würde Ihnen das zeigen:

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)

Und Sie können eine ähnliche Abfrage verwenden, um alle geerbten Elemente abzurufen:

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