Деревья в SQL

User avatar
Strannik223
Уже с Приветом
Posts: 569
Joined: 14 Dec 2003 04:06
Location: Львов->Киев->Торонто

Деревья в SQL

Post by Strannik223 »

Навеяно соседним топиком.

Для меня всегда было загадкой почему так сложно найти информацию по построению деревьев в плоских таблицах и оперированию ими в SQL. Эти "тайные знания" наверное передаются из поколения в поколение не будучи задокументироваными. Поэтому я решил описать что известно мне и приглашаю аксакалов править и дополнять.

Способ первый.
Наверное известный всем как ParentId column.
Добавляем в таблицу одну колонку, ParentID в которую записываем ID родителя. Для корневых элементов пишем туда NULL, или свой собственный ID (ParentID=ID). Я предпочитаю второй метод.

Преимущества: минимальная избыточность, +1 поле
Недостатки: для того что бы на стандартном SQL сделать выборку поддерева, а не только child, надо писать цикл. Это очень медленно. Некоторые базы предлагают иерархические запросы, но насколько я понимаю это тот же цикл но спрятаный и слегка может быть оптимизированый.

Способ второй: взят из книги Celko.
http://www.intelligententerprise.com/001020/celko.jhtml?_requestid=133465

В таблицу добавляем 2 поля, LeftNode, RightNode.
LeftNode содержит ID , RightNode ID - последнего потомка.
Выборка поддерева

Code: Select all

select * from MyObjects 
where LeftNode>=@id and Rightnode>=@id




Преимущества: легкая выборка поддерева.
Недостатки: при insert надо сделать
update MyObjects set RightNode=RightNode+1 where id>=@insertedID, то есть среднестатистически проапдейтить половину записей в таблице на одну вставку.

И метод третий, мой фаворит. Источник мне неизвестен, увидел на одном крупном проекте а потом и использовал сам.

Допустим у нас иерархия

Code: Select all

a->b->c
   |->d

Создаем дополнительную таблицу

Code: Select all

create table MyObject(id, Name, <other columns here>)
create table TreeStructure(ObjectId, ParentId, Level int)

insert into MyObject values(1, 'a')
insert into MyObject values(2, 'b')
insert into MyObject values(3, 'c')
insert into MyObject values(4, 'd')

insert into TreeStructure values(1,1,0) --a
insert into TreeStructure values(2,2,0) --b
insert into TreeStructure values(2,1,1) --b
insert into TreeStructure values(3,3,0) --c
insert into TreeStructure values(3,2,1) --c
insert into TreeStructure values(3,1,2) --c
insert into TreeStructure values(4,4,0) --d
insert into TreeStructure values(4,2,1) --d
insert into TreeStructure values(4,1,2) --d


Как видим иерархия вынесена в отдельнцю таблицу
Для каждого объекта в дополнительной таблице есть столько записей сколько у объекта предков.
Выборка поддерева

Code: Select all

select o.*
from TreeStructure ts
join MyObject o ON o.id=ts.ObjectId
where ts.ParentID=@id


Недостатки: некоторая избыточность, отдельная таблица с 2-мя полями (поле Level не является необходимым)
Несколько более сложный Insert, нужно вставить столько записей сколько предков у объекта.

Преимущества:
Возможность делать выборки с ограниченой по уровню вложеностью (AND Level<=@levels)
Пожалуй этот вариант дает самые гибкие возможности при постоении хитрых выборок. Так как дополнительная таблица содержит только коротикие колонки, обычно типа INT, то она очень быстрая а индексы не очень большие.
Возможность выбрать цепочку предков

Code: Select all

select o.Name
from TreeStructure ts
join MyObject o on o.id=ts.ParentID
where ts.ObjectID=@id
order by ts.Level


Выборка объектов не имеющих потомков (leaf)

Code: Select all

select 
from TreeStructure ts
where not exists (
     select *
      from TreeStructure childs
      where childs.ParentID=ts.ObjectID
)


Если известны иные способы, дополняйте!
Никакой разрухи нет. (с) Проф. Преображенский.
User avatar
Vladimir Kr.
Уже с Приветом
Posts: 539
Joined: 24 Mar 2004 07:31
Location: Krasnoyrsk -> -> Chicago

Post by Vladimir Kr. »

Людям SQL тяжело дается, а ты иерархические запросы требуешь ;)
Мы лучше нафигачим таблицу (r1 int, r2 int, ..., rn int), только главно DBA ее не показывать, а то у него истерика будет.

Деревья это не таблицы, деревья это сложно.
Наверное потому что требуются реже.

А вот в Oracle они давно живут
http://www.jlcomp.demon.co.uk/faq/connectby.html
и уже в Postgres начинают прорастать
http://gppl.terminal.ru/README.html
только поливать чаще надо

почему сложно найти?
google> sql "connect by" "start with"
User avatar
YellowMan
Уже с Приветом
Posts: 1099
Joined: 30 Sep 1999 09:01
Location: Bryansk,RUSSIA >> Dublin, Ireland

Post by YellowMan »

Воодще-то полно статей и в BOL и в Инете про деревья. На мой взгляд SQL лучше подходит для построения деревьев, поскольку позволяет одним заходом прочесть и отформатировать целый уровень :) - а способы, они от задачи зависят.
Удачи@С.Смирнов
User avatar
Strannik223
Уже с Приветом
Posts: 569
Joined: 14 Dec 2003 04:06
Location: Львов->Киев->Торонто

Post by Strannik223 »

Vladimir Kr. wrote:Людям SQL тяжело дается, а ты иерархические запросы требуешь ;)
Мы лучше нафигачим таблицу (r1 int, r2 int, ..., rn int), только главно DBA ее не показывать, а то у него истерика будет.

Деревья это не таблицы, деревья это сложно.
Наверное потому что требуются реже.

А вот в Oracle они давно живут
http://www.jlcomp.demon.co.uk/faq/connectby.html
и уже в Postgres начинают прорастать
http://gppl.terminal.ru/README.html
только поливать чаще надо

почему сложно найти?
google> sql "connect by" "start with"


1. С ДБА еще можно договорится, типа пива поставить, итд. А вот юзеров убедить что все тип топ сложнее, когда они результатов из за таймаута дождатся не могут. (Чувствую что вы сейчас предложите увеличить тайм аут :mrgreen: )

2."connect by" и иже с ними поддерживается далеко не всеми базами и как я отмечал они реализованы на основе того же цикла, что медленнее чем описаные 2 подхода, где делается выборка за 1 запрос, с участием индексов.
Никакой разрухи нет. (с) Проф. Преображенский.
User avatar
Strannik223
Уже с Приветом
Posts: 569
Joined: 14 Dec 2003 04:06
Location: Львов->Киев->Торонто

Post by Strannik223 »

YellowMan wrote:Воодще-то полно статей и в BOL и в Инете про деревья. На мой взгляд SQL лучше подходит для построения деревьев, поскольку позволяет одним заходом прочесть и отформатировать целый уровень :) - а способы, они от задачи зависят.


Насколько я помню в BOL только первый метод описан.
Никакой разрухи нет. (с) Проф. Преображенский.
zVlad
Уже с Приветом
Posts: 15311
Joined: 30 Apr 2003 16:43

Post by zVlad »

I have to replicate my posting here:
"Деревянным" и "реляционным" посвящается. Чтобы не забивать Привет длинными цитатами и пространными рассуждениями я просто решил посоветовать интересующимся вопросом представления иерархий в реляционных базах данных и методов работы с ними прочитать одно приложение с примерами из документации ДБ2 версии 8 (только обещайте прочитать все приложение если желаете прокоментировать, там всего несколько страниц):

http://www-306.ibm.com/cgi-bin/db2www/d...2w/en_main

Затем открываем ссылку: "SQL Reference Part. 1" (PDF document) и далее по оглавлению идем на "Appendix L".

Полробности синтаксиса для данного случая описаны в Chapter 4. Queries раздел Select-statement.
User avatar
Strannik223
Уже с Приветом
Posts: 569
Joined: 14 Dec 2003 04:06
Location: Львов->Киев->Торонто

Post by Strannik223 »

zVlad wrote:I have to replicate my posting here:


# DTWP001E: Net.Data is unable to locate the macro file d...2w.
Никакой разрухи нет. (с) Проф. Преображенский.
User avatar
cityzen
Уже с Приветом
Posts: 3759
Joined: 11 Feb 2004 13:37

Re: Деревья в SQL

Post by cityzen »

Strannik223 wrote:Допустим у нас иерархия

Code: Select all

a->b->c
   |->d

....
Выборка поддерева

Code: Select all

select o.*
from TreeStructure ts
join MyObject o ON o.id=ts.ObjectId
where ts.ParentID=@id




Как рапечатать поддерево так, чтобы после каждого "родителя" печатались "дети"? Типа:

a
- b
-- c
-- d
-e
-- f
One small step for me ...One giant leap for.. A frog?
zVlad
Уже с Приветом
Posts: 15311
Joined: 30 Apr 2003 16:43

Post by zVlad »

Strannik223 wrote:
zVlad wrote:I have to replicate my posting here:


# DTWP001E: Net.Data is unable to locate the macro file d...2w.


Try this:

http://www-306.ibm.com/cgi-bin/db2www/d ... 2w/en_main

Should work.
sp123
Уже с Приветом
Posts: 1962
Joined: 24 Feb 2001 10:01
Location: Челябинск -> Everett, WA

Re: Деревья в SQL

Post by sp123 »

cityzen wrote:
Strannik223 wrote:Допустим у нас иерархия

Code: Select all

a->b->c
   |->d

....
Выборка поддерева

Code: Select all

select o.*
from TreeStructure ts
join MyObject o ON o.id=ts.ObjectId
where ts.ParentID=@id




Как рапечатать поддерево так, чтобы после каждого "родителя" печатались "дети"? Типа:

a
- b
-- c
-- d
-e
-- f


Если данные организованы по методу 1 (одна таблица с полями id и parent_id, т.е. одинокое такое дерево).

Code: Select all


      SELECT  lpad('-',LEVEL-1,'-')||id
      FROM my_table
      CONNECT BY parent_id = PRIOR id
      START WITH id = <чего-нибудь>



Имелся в виду Oracle. Работает быстро.

Способ 2 никогда не использовал, статичные таблицы просто не попадались, хотя интересно.

Способ 3 (дерево вынесено в отдельную таблицу) - на самом то же самое, что и 1, просто используется в том случае, когда дерево не одно, а три тополя на плющихе. Поле level на самом деле там избыточно, имеет смысл только из соображений производительности.
User avatar
cityzen
Уже с Приветом
Posts: 3759
Joined: 11 Feb 2004 13:37

Re: Деревья в SQL

Post by cityzen »

sp123 wrote:
cityzen wrote:
Strannik223 wrote:Допустим у нас иерархия

Code: Select all

a->b->c
   |->d

....
Выборка поддерева

Code: Select all

select o.*
from TreeStructure ts
join MyObject o ON o.id=ts.ObjectId
where ts.ParentID=@id




Как рапечатать поддерево так, чтобы после каждого "родителя" печатались "дети"? Типа:

a
- b
-- c
-- d
-e
-- f


Если данные организованы по методу 1 (одна таблица с полями id и parent_id, т.е. одинокое такое дерево).

Code: Select all


      SELECT  lpad('-',LEVEL-1,'-')||id
      FROM my_table
      CONNECT BY parent_id = PRIOR id
      START WITH id = <чего-нибудь>





С Коннектом я и сам могу.
One small step for me ...One giant leap for.. A frog?
User avatar
YellowMan
Уже с Приветом
Posts: 1099
Joined: 30 Sep 1999 09:01
Location: Bryansk,RUSSIA >> Dublin, Ireland

Re: Деревья в SQL

Post by YellowMan »

Как рапечатать поддерево так, чтобы после каждого "родителя" печатались "дети"? Типа:

a
- b
-- c
-- d
-e
-- f



BOL:Expanding Hierarchies



Code: Select all

DECLARE @level int, @line char(20)
CREATE TABLE #stack (item char(20), level int)
INSERT INTO #stack VALUES (@current, 1)
SELECT @level = 1

WHILE @level > 0
BEGIN
   IF EXISTS (SELECT * FROM #stack WHERE level = @level)
      BEGIN
         SELECT @current = item
         FROM #stack
         WHERE level = @level
         SELECT @line = space(@level - 1) + @current
         PRINT @line
         DELETE FROM #stack
         WHERE level = @level
            AND item = @current
         INSERT #stack
            SELECT child, @level + 1
            FROM hierarchy
            WHERE parent = @current
         IF @@ROWCOUNT > 0
            SELECT @level = @level + 1
      END
   ELSE
      SELECT @level = @level - 1
END -- WHILE
Удачи@С.Смирнов
User avatar
wolfboy
Уже с Приветом
Posts: 1224
Joined: 24 Feb 2003 07:40

Post by wolfboy »

Могу добавить еще один способ хранения дерева в таблице
create table MyObject(id, Name, <other columns here>)
где id строка, имеющая структуру
RootKeyId+NextKeyId+... +OurKeyId
Для каждого поддерева нумерация начинается, например с "00".
Каждый KeyId- двухсимвольное поле, чего хватает для >10000 ID (естественно, можно использовать 3-4 символьные поля)

Недостатки -
число уровней дерева ограничено шириной Id/2
избыточная ширина id

Достоинства:
Просто формируются как выборка по всем детям, так и выборка по всем потомкам (без рекурсий)

Return to “Вопросы и новости IT”