Pull to refresh

Comments 15

понадобится какой-нибудь там хитрый JOIN, но нет

Ясно-понятно. Мы не умеем в джойн, поэтому сделаем подзапрос. Может сразу CTE прикрутить?

Задача решается элементарным селфджойном, чтобы проверить потомка на нулл. Парент уже есть в полях. Дальше можно и кейсом, можно и ифом выдать нужное значение

Встречался с такой задачей часто на больших таблицах. Безусловно, должен быть join. С масштабируемостью in (...) - начинаются проблемы от 10к+ элементов, к тому же где-то этот массив в памяти надо разместить. Ну и P должен быть проиндексирован, а иначе Nested loops.

Причем селфджойн скорее всего будет эффективней:
Оптимизатор скорее всего сделает его hash-join-ом (https://habr.com/ru/articles/657021/). Вместо ужасного N IN (SELECT DISTINCT P FROM Tree) внутри CASE, с которым не понятно как оптимизатор справится, даже при наличии индекса по P... А без индекса вообще мрак на большой таблице.

Self-join тут не стреляет. У узла может быть много детей, что породит дубликаты, а DISTINCT - это дорого.

В заголовке и в тексте упоминается бинарное дерево, так что не так уж и много.

И DISTINCT можно сделать до джойна.

Так себе решение... скорее всего оно будет медленное, и чем больше таблица, тем хуже производительность. Коррелированный WHEN EXISTS смотрится выгоднее - особенно если имеются соотв. индексы.

Классика жанра. :) На sql.ru была даже прибитая гвоздиком тема для поиска всех потомков дерева и не обязательно бинарного на переменных и в один проход по таблице... эх, жаль закрылся скуль-ру..

Открылась его копия, и не одна.

https://resql.ru

Тут даже регистрироваться можно, но активность нулевая, все в телеграм ушли.

спасибо, не знал. добавил себе вкладку

Глянул, к сож. веток прибитых сверху гвоздиком не обнаружил, а искать поиском как-не задалось. Давно .. 2011-2014, где-то там, решение Бочкова, которое искалось "всем миром".

Пардон, нашлось (стоило вспомнить Бочкова) https://resql.ru/forum/topic.php?fid=47&tid=1830048 :) Тут много разных решений..

А можно альтернативные решения в студию? Я современный SQL не знаю, мне бы глянуть возможности.

А еще если бы бенчмарк сделать, и определить, кто прав по вопросу быстродействия. Тема для продолжения статьи.

https://gist.github.com/portnov/2e5d0522e26513d7f594df4fe73ae862

Надо заметить, распределение мест зависит от объёма данных, от степени ветвления дерева... на небольшом наборе данных выигрывает предложенный в статье запрос. На 1млн строк выигрывает "ручное" решение с union all.

проще некуда без IN на t-sql :

CREATE TABLE #Tree (
    N int,
    P int
);
INSERT INTO #Tree 
VALUES
( 1, 2),
( 3, 2),
( 6, 8),
(	9, 8),
(	2, 5),
(	8, 5),
(	5, null);
----SELECT * FROM #Tree;
declare @root1 int
select @root1= N from #Tree where p  is null

select N,'корень' from #Tree where p  is null

select P,'ветвь' from #tree 
  where  P != @root1
  group by p

select t.N,'лист' from #tree t
 left join  #tree u
  on t.n = u.p
  where  u.n is null
 
drop table #Tree

Рекомендую такое решение:

Hidden text
SELECT N AS Node,
       CASE
           WHEN T.P IS NULL THEN 'Root'
           WHEN S.P IS NOT NULL THEN 'Inner'
           ELSE 'Leaf'
       END AS "Type"
FROM Tree T
LEFT JOIN
(
   SELECT DISTINCT P FROM Tree
) S on S.P = T.N
;

тут всего два скана и один хэш джоин, и подойдет практически для любой базы

Часто на практике нужно возвратить также уровень элемента из такой таблицы. В примере из статьи для промежуточных элементов мы получим только информацию о том, что он не конечный и не начальный. Возвратить таблицу вида "Node", "Parent", "Level" из исходной с произвольным количеством промежуточных уровней можно рекурсивным запросом.

with cte as (
select N as [Node]
      ,P as [Parent]
	  ,1 as [Level]
  from t1 as d
  where d.P is null
union all
select d1.N
      ,d1.P
	  ,cte.[Level]+1 as lvl
  from t1 as d1 inner join cte on d1.P=cte.[Node])
select * from cte

Sign up to leave a comment.

Articles