Ако използвате MySQL 8.0 или MariaDB 10.2 (или по-високо) можете да опитате рекурсивни CTEs (изрази за обща таблица) .
Приемайки следната схема и данни:
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);
Сега се опитвате да вмъкнете нов ред с child_id = 1
и parent_id = 4
. Но това би създало циклични отношения (1->4->2->1 и 1->4->3->1 ), което искате да предотвратите. За да разберете дали вече съществува обратна връзка, можете да използвате следната заявка, която ще покаже всички родители на списък 4 (включително наследени/преходни родители):
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
Резултатът би бил:
child_id | parent_id
4 | 2
4 | 3
2 | 1
3 | 1
Можете да видите в резултата, че списък 1 е един от родителите на списък 4 , и не бихте вмъкнали новия запис.
Тъй като искате да знаете само дали списък 1 е в резултата, можете да промените последния ред на
select * from rcte where parent_id = @new_child_id limit 1
или до
select exists (select * from rcte where parent_id = @new_child_id)
BTW:Можете да използвате същата заявка, за да предотвратите излишни връзки. Ако приемем, че искате да вмъкнете записа с child_id = 4
и parent_id = 1
. Това би било излишно, тъй като списък 4 вече наследява списък 1 над списък 2 и списък 3 . Следната заявка ще ви покаже, че:
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)
И можете да използвате подобна заявка, за да получите всички наследени елементи:
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