sql — Удаление в таблице закрытия с несколькими одинаковыми путями

#sql #postgresql #transitive-closure-table

Вопрос:

У меня есть следующая иерархическая структура:

 A -> E -> C -> D
|
|
|-> B -> D
 

Вот таблица закрытия, которую я придумал:

 | Ancestor | Descendant | Depth |
|    A     |     A      |   0   |
|    B     |     B      |   0   | 
|    C     |     C      |   0   | 
|    D     |     D      |   0   | 
|    E     |     E      |   0   | 
|    A     |     E      |   1   | 
|    A     |     B      |   1   | 
|    A     |     C      |   2   | 
|    E     |     C      |   1   | 
|    A     |     D      |   3   | 
|    E     |     D      |   2   | 
|    C     |     D      |   1   | 
|    A     |     D      |   2   | 
|    B     |     D      |   1   | 
 

Я хочу удалить связь между B и D , и поэтому я хочу удалить связь между A и D (связь глубины 2 ). Проблема в том, что я не хочу удалять связь между A и D глубиной 3 , так как я не удалял связь между C и D .

На данный момент вот инструкция SQL для перечисления ссылок, которые я хочу удалить:

 SELECT link.ancestor, link.descendant, link.depth
FROM closure_table p,
     closure_table link,
     closure_table c
WHERE p.ancestor = link.ancestor
  AND c.descendant = link.descendant
  AND p.descendant = B
  AND c.ancestor = D;
 

но это утверждение дает мне строки, которые я не хочу удалять:

 | Ancestor | Descendant | Depth |
|    A     |     D      |   2   |
|    A     |     D      |   3   |  <- As said before, I want to keep this one
|    B     |     D      |   1   | 
 

Комментарии:

1. Помечайте только ту базу данных, которую вы используете.

Ответ №1:

Вы можете выбрать пару предок-потомок, которая имеет минимальную глубину всех тех же пар предок-потомок:

 with edges(s, e) as (
   -- the pairs to be removed
   select 'A', 'D'
   union all
   select 'B', 'D'
),
n_l as (
   select c.* from closure c where c.ancestor != c.descendant
)
select c.* from n_l c where exists (select 1 from edges e where e.s = c.ancestor and e.e = c.descendant) 
and c.depth = (select min(c1.depth) from n_l c1 where c1.ancestor = c.ancestor and c1.descendant = c.descendant);
 

Выход:

предок потомок глубина
A D 2
B D 1

Комментарии:

1. Это решение работает для данной конкретной ситуации, но, по-моему, работает не во всех ситуациях

Ответ №2:

Я думаю, что нашел решение для тех, кто заинтересован:

 declare @Descendant nchar(10) = 'D';
declare @Ancestor nchar(10) = 'B';

with cte as
(
  select Ancestor, Depth
  from closure_table
  where Descendant = @Descendant
  and Ancestor = @Ancestor
  and Depth = 1
union all
  select r.Ancestor, l.Depth   1 as Depth
  from cte as l
  join closure_table as r on r.Descendant = l.Ancestor
  where r.Depth = 1
)
delete closure_table
from closure_table
join cte on cte.Ancestor = closure_table.Ancestor and cte.Depth = closure_table.Depth
where closure_table.Descendant = @Descendant;