30
Иерархические структуры в реляционных базах данных: Adjacency List (Вложенные множества) • Adjacency List; • Materialized Path; • Nested Sets; • Так что же использовать?; Иерархические структуры в реляционных базах данных

Вебинар Томулевича adjacency

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Вложенные множества)

• Adjacency List;• Materialized Path;• Nested Sets;• Так что же использовать?;

Иерархические структуры в реляционных базах данных

Page 2: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Вложенные множества)

• Метод хранения Adjacency List;• Виды наследования;• Использование;• Управление;

Adjacency List (Смежные вершины)

Page 3: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Метод хранения Adjacency List

id pid

id pid id pid pid

id pid id pid id pid id pid id pid

id

Схема:

Правила: • Каждому pid соответствует свой id;• На уровне узла pid отличен от id;• Для корневых узлов pid IS NULL;• Узел не должен подчиняться своему потомку.

Свойства: • Для поддержки структуры дерева требуется 2 поля (id и pid);• Узел напрямую связан с родителем;• С узлом напрямую связаны дети;

Page 4: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

В качестве примеров:

Рубрикатор:

• Товары• Мониторы

• Samsung• 15”• 17”• …

• NEC• 15”• 17”• …

• ViewSonic• 15”• 17”• …

• …• Принтеры

• Samsung• …

• HP• …

• …

Сообщение 1• комментарий 1.1

• ответ на комментарий• …

• … • комментарий 1.2

• ответ на комментарий • …

• …

Сообщение 2• комментарий 2.1

• ответ на комментарий• …

• … • комментарий 2.2

• ответ на комментарий • …

• ……

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

Page 5: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Виды наследований

1. Одиночное наследование:

1. Множественное наследование:

id pid

id pid id pid pid

id pid id pid id pid id pid id pid

id

id pid

id pids id pids pids

id pids id pids id pids id pids id pids

id

id pidid pid id pidid pid

pidsidpidsid ?

id INTEGERpid INTEGER…

id INTEGERpid INTEGER[]…

?

Page 6: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Множественное наследование

id pid

id pids id pids pids

id pids id pids id pids id pids id pids

id

id pidid pid id pidid pid

pidsidpidsid

id pid

pidid pididpidid pididpidid pididpidid pidid

pidid pididpidid pidid

id pid

Приведение к одиночному наследованию:

Page 7: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Множество деревьев

id pidid pid

id pid id pid

id pid

id pidid pid

id pid

id pidid pid

id pid

id pidid pid

id pid

Tree 1 Tree 2

tid

tid tidtid

tid

tid

Trees table (сообщения)

tid tid tid tid tid tid tid tid

Page 8: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Множественное наследование:

id INTEGERpid INTEGERiid INTEGER

Структуры таблиц

id INTEGER…

tree items

Множество деревьев:

id INTEGERpid INTEGERtid INTEGER

id INTEGER…

tree treesWHERE

JOIN

Page 9: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency List

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Рубрикатор:

• Товары• Мониторы

• Samsung• 15”• 17”• …

• NEC• 15”• 17”• …

• ViewSonic• 15”• 17”• …

• …• Принтеры

• Samsung• …

• HP• …

• …

Сообщение• комментарий 1.1

• ответ на комментарий• ответ на ответ

• …• …

• комментарий 1.2• ответ на комментарий

• ответ на ответ• …

• …• …

• …

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

Задачи:

• Родительский узел;• Подчиненные узлы;• Родительская ветка;• Подчиненная ветка;

Page 10: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency List

Задачи:

• Родительский узел;• Подчиненные узлы;• Родительская ветка;• Подчиненная ветка;

Page 11: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency List

Получение смежных узлов:

Родительский узел:

SELECT * FROM my_tree WHERE id = [pid];

Подчиненные узлы:

SELECT * FROM my_tree WHERE pid = [id];

Page 12: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение родительской ветви:

С использованием JOIN:

SELECT * FROM my_tree AS l4 JOIN my_tree AS l3 ON l3.id = l4.pid JOIN my_tree AS l2 ON l2.id = l3.pid JOIN my_tree AS l1 ON l1.id = l2.pid ... WHERE l4.id = [item.pid];

С использованием UNION:

SELECT l1.*, 1 AS level FROM my_tree AS l1 WHERE l1.id = [item.pid] UNION SELECT l2.*, 2 AS level FROM my_tree AS l1 JOIN my_tree AS l2 ON l2.id = l1.pid WHERE l1.id = [item.pid] UNION SELECT l3.*, 3 AS level FROM my_tree AS l1 JOIN my_tree AS l2 ON l2.id = l1.pid JOIN my_tree AS l3 ON l3.id = l2.pid WHERE l1.id = [item.pid]...ORDER BY level DESC;

Page 13: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение родительской ветви:

С использованием WITH RECURSIVE (PostgreSQL 8.4):

WITH RECURSIVE parents AS ( SELECT *, ARRAY[t.id] AS exist, FALSE AS cycle FROM my_tree AS t WHERE id = [item.pid] UNION ALL SELECT t.*, exist || t.id, t.id = ANY(exist) FROM parents AS p, my_tree AS t WHERE t.id = p.pid AND NOT cycle ) SELECT * FROM parents WHERE NOT cycle LIMIT [max_deep];

WITH RECURSIVE мы создаем рекурсивный подзапрос parents.Первый запрос рекурсии, в нем указываем точку отсчета. Второй запрос, собственно, рекурсивный запрос. Поле exist - массив уже полученных id, который мы проверяем в поле cycle, что бы не уйти в бесконечную рекурсию. LIMIT ограничивает глубину выборки ветки, так как родитель у узла может быть только лишь один.

Page 14: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение родительской ветви (на уровне приложения):

Рекурсивная функция:...sub parent_recurse { my %params = @_;# Текущая глубина рекурсии $params{deep} ||= 1;# Хеш ранее выбранных узлов $params{exist} ||= {};# Возвращаем пустой список если превысили максимальную заданную глубину рекурсии return () if $params{deep} && $params{max_deep} && $params{deep} > $params{max_deep};# делаем запрос к базе данных на поллучение следующего узла my $query = 'SELECT * FROM my_tree WHERE id = ' . $params{pid}; my $sth = $dbh->prepare($query); $sth->execute; my $item = $sth->fetchrow_hashref; $sth->finish;# Объявляем внутренний список узлов – родителей my @parents = ();# Если узел найден и у него явно есть родитель, то if ($item && $item->{pid}) {# Для начала проверим, а не выбирали ли мы уже такой родительский узел return () if $params{exist}->{$item->{pid}};# Не выбирали, тогда добавляем в хеш $params{exist}->{$item->{id}} = 1;# И вызываем снова рекурсивную функцию @parents = &parent_recurse( pid => $item->{pid}, max_deep => $params{max_deep}, deep => $params{deep} + 1, exist => $params{exist} ); }# Добавлем выбранный узел к массиву, если есть push @parents, $item if $item;# Возвращаем список выбранных узлов return @parents;}...my @parents = &parent_recurse(pid => $item->{pid}, max_deep => 2);...

Page 15: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение родительской ветви (на уровне приложения):

Динамический массив:

...# Объявляем пустой список родителейmy @parents = ();# Объявляем хеш уже полученных узлов, и добавляем в него id текущего узлаmy %exist = ($item->{id} => 1);# Объявляем динамический массив и вносим в него текущий узелmy @while = ($item);# Максимальная глубина выборкиmy $max_deep = 3;# Текущая глубина выборкиmy $deep = 1;# В цикле отрезаем первый эмемент динамического массива до тех пор пока это возможноwhile (my $item = shift @while && $deep <= $max_deep) {# Прерываем цикл, если узла явно нет родителя или такого родителя мы уже получали last if !$item->{pid} || $exist{$item->{pid}};# Делаем запрос к базе данных my $query = 'SELECT * FROM my_tree WHERE id = ' . $item->{pid}; my $sth = $dbh->prepare($query); $sth->execute; my $parent = $sth->fetchrow_hashref; $sth->finish;# Прерываем цикл, если узел не получили last unless $parent;# Добавляем узел в массив родителей push @parents, $parent;# Добавляем id узла в хеш полученных узлов $exist{$parent->{id}} = 1;# Добавляем узел в динамический массив push @while, $parent;# Инкрементим текущую глубину $deep++;}...

Page 16: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение родительской ветви:

Хранимая процедура PostgreSQL:CREATE OR REPLACE FUNCTION "public"."get_parents" ( pid_in INTEGER, deep INTEGER) RETURNS SETOF "public"."my_tree" AS$body$DECLARE exist_ids INTEGER[] := ARRAY[0]; -- Для пустого массива плохо работает ALL pid_now INTEGER := pid_in; -- Текущий pid deep_now INTEGER := deep; -- Текущаа глубина item my_tree;BEGIN WHILE pid_now IS NOT NULL AND pid_now > 0 AND pid_now <> ALL (exist_ids) AND (deep_now IS NULL OR deep_now > 0) LOOP SELECT * INTO item FROM my_tree WHERE id = pid_now; IF item.id IS NULL THEN EXIT; END IF; RETURN NEXT item; pid_now := item.pid; exist_ids := exist_ids || item.id; IF deep_now IS NOT NULL THEN deep_now := deep_now – 1; END IF; END LOOP; RETURN;END;$body$LANGUAGE 'plpgsql';

-- Для красоты добавим функцию с одним передаваемым параметромCREATE OR REPLACE FUNCTION "public"."get_parents" ( pid_in INTEGER) RETURNS SETOF "public"."my_tree" AS$body$SELECT * FROM get_parents( $1, NULL );$body$LANGUAGE 'sql';

Page 17: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение родительской ветви:

Хранимая процедура MySQL:

CREATE DEFINER = CURRENT_USER PROCEDURE `get_parents`( IN pid_in INTEGER, IN max_deep INTEGER )BEGIN DECLARE id_now INTEGER; DECLARE pid_now INTEGER DEFAULT pid_in; DECLARE field1_now TEXT; DECLARE exist TEXT DEFAULT '|'; WHILE pid_now > 0 AND exist NOT LIKE CONCAT('%|', pid_now, '|%') AND (max_deep IS NULL OR max_deep > 0) DO SELECT * INTO id_now, pid_now, field1_now FROM my_tree WHERE id = pid_now; SELECT id_now, pid_now, field1_now; IF (max_deep IS NOT NULL) THEN SET max_deep = max_deep - 1; END IF; SET exist = CONCAT(exist, id_now, '|'); END WHILE;END;

Примечание: Возвращается несколько result set.

Page 18: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение дочерней ветви:

С использованием WITH RECURSIVE (PostgreSQL 8.4):

WITH RECURSIVE children AS ( SELECT *, pid || '|' || [order_field] AS ord, ARRAY[id] AS exist, FALSE AS cycle FROM my_tree WHERE pid = [item.id] UNION ALL SELECT t.*, ord || '.' || t.pid || '|' || t.[order_field], exist || t.id, t.id = ANY(exist) FROM children AS c, my_tree AS t WHERE t.pid = c.id AND NOT cycle AND array_length(exist, 1) < [max_deep]) SELECT * FROM children WHERE NOT cycle ORDER BY ord LIMIT [limit];

Где:• [order_field] - поле сортировки в пределах подчинения;• [item.id] - ID узла от которого производится выборка;• [max_deep] - максимальная глубина рекурсии;• [limit] - количество выбираемых строк;Основной особенностью этого запроса является дополнительное поле path, которое практически является Materialized path, за исключением того, что мы добавляем в него дополнительное поле сортировки текущего узла.Причем защита от зацикливания (поля exist и cycle) локализована в пределах отдельных ветвей, поэтому это хоть и предохранит от бесконечного зацикливания, но позволит повторно выбрать строки, так что будте внимательны.

Page 19: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение дочерней ветви (на уровне приложения):

Рекурсивная функция:

...sub child_recurse { my %params = @_;# Текущая глубина рекурсии $params{deep} ||= 1;# Хеш ранее выбранных узлов $params{exist} ||= {};# Возвращаем пустой список если превысили максимальную заданную глубину рекурсии return () if $params{deep} && $params{max_deep} && $params{deep} > $params{max_deep};# Объявляем внутренний список узлов – потомков my @children = ();# делаем запрос к базе данных на получение списка подчиненных узлов my $query = 'SELECT * FROM my_tree WHERE pid = ' . $params{pid} . ($params{order} ? ' ORDER BY ' . $params{order} : ''); my $sth = $dbh->prepare($query); $sth->execute; while (my $item = $sth->fetchrow_hashref) {# Для начала проверим, а не выбирали ли мы уже такой узел next () if $params{exist}->{$item->{id}};# Не выбирали, тогда добавляем в хеш $params{exist}->{$item->{id}} = 1;# Добавлем выбранный узел к массиву push @children, $item;# Вызываем снова рекурсивную функцию для получения подчиненных узлов текущего узла my @children_deep = &child_recurse( pid => $item->{id}, max_deep => $params{max_deep}, order => $params{order}, deep => $params{deep} + 1, exist => $params{exist},);# Добавляем список подчиненных узлов текущего узла push @children, @children_deep; } $sth->finish;# Возвращаем список выбранных узлов return @children;}

my @children = &child_recurse(pid => $item->{id}, max_deep => 5, order => 'id'); ...

Page 20: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение дочерней ветви (на уровне приложения):

Оптимизация выборки:

SELECT children.* FROM my_tree AS children JOIN my_tree AS parents ON parents.id = children.pid WHERE children.pid = ANY([parents_array]) ORDER BY parents.[order_field], children.[order_field];

Ввести денормализацию таблицы создав дополнительное поле счетчика потомков. В этом случае можно будет проверять наличие потомков и делать или нет запрос на их получение. Это решение рассмотрено более детально ниже;

Производить выборки не построчно, а списочно, для каждого уровня вложенности. Это решение подробно рассмотрим здесь;

Где:[parents_array] - массив id родительских узлов;[order_field] - поле сортировки;

Page 21: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Использование Adjacency ListПолучение дочерней ветви (на уровне приложения):

...my $max_deep = 5;my $order = 'field1‘;my $limit = 20; my @children = (); my @while = ($item); my %exist = (); my %indexes = (); my $skew = 0; my $pids = []; my $deep_now = 1; while (my $child = shift @while) {next if $exist{$child->{id}}; $exist{$child->{id}} = 1; push @$pids, $child->{id}; unless (defined $indexes{$child->{pid} || 'NULL'}) { if ($child ne $item) { push @children, $child; $indexes{$child->{id}} = $#children; } } else { splice @children, $indexes{$child->{pid}} + $skew + 1, 0 , ($child); $indexes{$child->{id}} = $indexes{$child->{pid}} + $skew + 1; $skew++; } unless ($while[0]) { $skew = 0; $deep_now++; last if $max_deep && $deep_now > $max_deep; my $query = 'SELECT children.* FROM my_tree AS children JOIN my_tree AS parents ON parents.id = children.pid WHERE children.pid = ANY(?) ORDER BY parents.’.$order.', children.’.$order.($limit ? ' LIMIT ‘.$limit : ''); my $sth = $dbh->prepare($query); $sth->execute($pids); while (my $child = $sth->fetchrow_hashref) {push @while, $child} $sth->finish; }}...

Динамический массив:

Page 22: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListОсновные действия:

Дополнительные действия:

• Контроль правил;• Денормализация (счетчики и уровень).

• Изменение pid узла;

Page 23: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListКонтроль подчиненности:

Триггер PostgreSQL 8.4 и выше:

CREATE OR REPLACE FUNCTION "public"."my_tree_update_trigger" () RETURNS trigger AS$body$DECLARE tmp_id INTEGER;BEGIN-- Если произошло изменение родителя узла IF NEW.pid IS NOT NULL AND (NEW.pid <> OLD.pid OR OLD.pid IS NULL) THEN-- Пытаемся найти в родителькой ветки нового родителя текущий узел WITH RECURSIVE parents AS ( SELECT t.id, t.pid, ARRAY[t.id] AS exist, FALSE AS cycle FROM my_tree AS t WHERE id = NEW.pid UNION ALL SELECT t.id, t.pid, exist || t.id, t.id = ANY(exist) FROM parents AS p, my_tree AS t WHERE t.id = p.pid AND NOT p.cycle ) SELECT id INTO tmp_id FROM parents WHERE id = NEW.id AND NOT cycle LIMIT 1;-- Узел найден, следовательно родителя назначить не можем IF tmp_id IS NOT NULL THEN RAISE NOTICE 'Нельзя ставить потомком родителя!'; NEW.pid := OLD.pid; END IF; END IF; RETURN NEW;END;$body$LANGUAGE 'plpgsql';

CREATE TRIGGER "my_tree_update" BEFORE UPDATE ON "public"."my_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."my_tree_update_trigger"();

Page 24: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListКонтроль подчиненности:

Триггер PostgreSQL ниже 8.4:

CREATE OR REPLACE FUNCTION "public"."my_tree_update_trigger" () RETURNS trigger AS$body$DECLARE tmp_id INTEGER;BEGIN-- Если произошло изменение родителя узла IF NEW.pid IS NOT NULL AND (NEW.pid <> OLD.pid OR OLD.pid IS NULL) THEN-- Пытаемся найти в родителькой ветки нового родителя текущий узел SELECT id INTO tmp_id FROM get_parents(NEW.pid) WHERE id = NEW.id LIMIT 1;-- Узел найден, следовательно родителя назначить не можем IF tmp_id IS NOT NULL THEN RAISE NOTICE 'Нельзя ставить потомком родителя!'; NEW.pid := OLD.pid; END IF; END IF; RETURN NEW;END;$body$LANGUAGE 'plpgsql';

CREATE TRIGGER "my_tree_update" BEFORE UPDATE ON "public"."my_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."my_tree_update_trigger"();

Page 25: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListДенормализация counter и level:

Триггер на INSERT (PostgreSQL):CREATE OR REPLACE FUNCTION "public"."my_tree_insert_trigger" () RETURNS trigger AS$body$DECLARE parent my_tree;BEGIN-- Что денормализовано, то ставить вручную нельзя NEW.counter := 0; NEW.level := 0;-- Если определен pid тогда обновляем счетчик родителя IF NEW.pid IS NOT NULL OR NEW.pid > 0 THEN-- Сразу вернем результат обновления родителя-- Финт ушами с CASE при обновлении счетчика из-за того что NULL + 1 = NULL, -- поэтому эти поля лучше сразу делать NOT NULL, а сейчас перестрахуемся UPDATE my_tree SET counter = CASE WHEN counter IS NULL THEN 1 ELSE counter + 1 END WHERE id = NEW.pid RETURNING * INTO parent;-- Проверим существование родителя, без отмены операции IF parent.id IS NULL THEN RAISE NOTICE 'ОШИБКА! Родителя с таким ID нет (%)', NEW; NEW.pid := 0; ELSE-- Устанавливаем значение level для вставляемого узла NEW.level := CASE WHEN parent.level IS NULL OR parent.level = 0 THEN 1 ELSE NEW.level + 1 END; END IF; END IF; RETURN NEW;END;$body$LANGUAGE 'plpgsql';

CREATE TRIGGER "my_tree_insert" BEFORE INSERT ON "public"."my_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."my_tree_insert_trigger"();

Page 26: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListДенормализация counter и level:

Триггер на UPDATE (PostgreSQL):CREATE OR REPLACE FUNCTION "public"."my_tree_update" () RETURNS trigger AS$body$DECLARE parent my_tree; child my_tree; level_skew INTEGER; pids INTEGER[]; tmp_pids INTEGER[]; exist_update INTEGER[];BEGIN-- Опять же для предотвращения сравнения с NULL ставим 0 IF OLD.pid IS NULL THEN OLD.pid := 0; END IF; IF NEW.pid IS NULL THEN NEW.pid := 0; END IF; IF OLD.level IS NULL THEN OLD.level := 0; END IF;-- Если произошла смена родителя IF NEW.pid <> OLD.pid THEN-- Уменьшаем счетчик старого родителя если он есть IF OLD.pid > 0 THEN UPDATE my_tree SET counter = counter - 1 WHERE id = OLD.pid; END IF;-- Увеличиваем счетчик нового родителя если он есть IF NEW.pid > 0 THEN UPDATE my_tree SET counter = COALESCE(counter, 0) + 1 WHERE id = NEW.pid RETURNING * INTO parent;-- Проверяем существование родителя IF parent.id IS NULL THEN RAISE NOTICE 'ОШИБКА! Родителя с таким ID нет (%)', NEW; NEW.level := 0; NEW.pid := 0; ELSE-- Если родитель есть то устанавливаем новый уровень узла NEW.level := COALESCE(parent.level, 0) + 1; END IF; ELSE NEW.level := 0; END IF;-- Данные по перемещаемому узлу обновили, теперь следует обновить-- level потомков перемещаемого узла level_skew := NEW.level - OLD.level;...

Page 27: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListДенормализация counter и level:

Триггер на UPDATE (PostgreSQL), продолжение:...-- Смещение уровня таки есть IF level_skew <> 0 THEN pids := ARRAY[NEW.id]; exist_update := ARRAY[NEW.id]; -- Пока есть узлы у которых есть потомки: WHILE pids[1] IS NOT NULL LOOP tmp_pids := ARRAY[NULL];-- Обновляем всех потомков на уровне FOR child IN UPDATE my_tree SET level = level + level_skew WHERE pid = ANY (pids) AND id <> ALL (exist_update) RETURNING * LOOP-- Поставим защиту для того, что бы невозможно было поставить узел в починение своему потомку IF child.id = NEW.pid THEN RAISE EXCEPTION 'Ошибка! Нельзя ставить узел в подчинение потомку! (%)', NEW; RETURN NULL; END IF;-- Если у потомка еще есть потомки, то добавляем его id в список на обновление-- следующего уровня IF child.counter IS NOT NULL AND child.counter > 0 THEN tmp_pids := array_prepend(child.id, tmp_pids); END IF;-- Защита от повторов exist_update := array_append(exist_update, child.id); END LOOP; pids := tmp_pids; END LOOP; END IF; END IF; RETURN NEW;END;$body$LANGUAGE 'plpgsql'; 

CREATE TRIGGER "my_tree_update" BEFORE UPDATE ON "public"."my_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."my_tree_update"();

Page 28: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListДенормализация counter и level:

Триггер на DELETE (PostgreSQL) :

CREATE OR REPLACE FUNCTION "public"."my_tree_delete_trigger" () RETURNS trigger AS$body$BEGIN-- Если есть родитель, то обновляем его счетчик IF OLD.pid IS NOT NULL AND OLD.pid > 0 THEN UPDATE my_tree SET counter = counter – 1 WHERE id = OLD.pid; END IF; IF current_setting('user_vars.tree_delete') = 'levelup' THEN UPDATE my_tree SET pid = OLD.pid WHERE pid = OLD.id; ELSIF current_setting('user_vars.tree_delete') = 'root' THEN UPDATE my_tree SET pid = 0 WHERE pid = OLD.id; ELSE IF OLD.counter IS NOT NULL AND OLD.counter > 0 THEN DELETE FROM my_tree WHERE pid = OLD.id; END IF; END IF; RETURN OLD;END;$body$LANGUAGE 'plpgsql';

CREATE TRIGGER "my_tree_delete" AFTER DELETE ON "public"."my_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."my_tree_delete_trigger"();

Page 29: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Управление Adjacency ListДенормализация counter и level:

Способы удаления ветвей:

BEGIN;SELECT set_config('user_vars.tree_delete', 'levelup', TRUE);DELETE FROM my_tree WHERE id = [item.id];COMMIT;

1. Транзакционный:

DELETE FROM my_tree USING ( SELECT set_config('user_vars.tree_delete', 'root', TRUE) ) AS conf WHERE id = [item.id];

1. Не транзакционный:

Page 30: Вебинар Томулевича adjacency

Иерархические структуры в реляционных базах данных: Adjacency List (Смежные вершины)

Вопросы?

Карикатуры: Сергей Корсун

Статьи по теме: http://doc.prototypes.ru/database/trees/

Сергей ТомулевичRambler, Москва, 2010