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

Вебинар Томулевича materialized.path 2

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Вебинар Томулевича materialized.path 2

1

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

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

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

2

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

Materialized Path (Материализованный путь)

Page 3: Вебинар Томулевича materialized.path 2

3

Метод хранения Materialized Path

Схема:

Правила: • Все пути уникальны;• Все части пути от начала до разделителей должны присутствовать в базе;• Элемент пути не может содержать разделитель (точку);

Свойства: • Для поддержки структуры дерева требуется 1 поле (path);• Узлы напрямую не связаны;• Родительский путь частично входит в дочерние;

path1

path1.path3path1.path2 path1.path4

path1.path3.path7path1.path2.path6path1.path2.path5 path1.path4.path9path1.path4.path8

Page 4: Вебинар Томулевича materialized.path 2

4

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

Рубрикатор:

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

• Samsung• 15”• 17”• …

• NEC• 15”• 17”• …

• ViewSonic• 15”• 17”• …

• …• Принтеры

• Samsung• …

• HP• …

• …

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

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

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

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

• …

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

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

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

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

• ……

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

Page 5: Вебинар Томулевича materialized.path 2

5

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

Рубрикатор:

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

• Samsung• 15”• 17”• …

• NEC• 15”• 17”• …

• ViewSonic• 15”• 17”• …

• …• Принтеры

• Samsung• …

• HP• …

• …

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

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

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

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

• …

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

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

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

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

• ……

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

Page 6: Вебинар Томулевича materialized.path 2

6

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

Наследование по вхождению:

goodsgoods.monitorsgoods.monitors.nec.15goods.monitors.nec.17goods.monitors.nec.19goods.monitors.samsunggoods.monitors.samsung.15goods.monitors.samsung.17goods.monitors.samsung.19goods.monitors.viewsonic.15goods.monitors.viewsonic.17goods.monitors.viewsonic.19goods.printersgoods.printers.hpgoods.printers.hp.incgoods.printers.hp.lasergoods.printers.samsunggoods.printers.samsung.laser

Page 7: Вебинар Томулевича materialized.path 2

7

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

goodsgoods. monitorsgoods. monitors. necgoods. monitors. nec. 17goods. monitors. nec. 19goods. monitors. samsunggoods. monitors. samsung. 15goods. monitors. samsung. 17goods. monitors. samsung. 19goods. monitors. viewsonicgoods. monitors. viewsonic. 17goods. monitors. viewsonic. 19goods. printersgoods. printers. hpgoods. printers. hp. incgoods. printers. hp. lasergoods. printers. samsunggoods. printers. samsung. laser

Наследование по вхождению:

Page 8: Вебинар Томулевича materialized.path 2

8

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

goodsgoods. monitorsgoods. monitors. necgoods. monitors. nec. 17goods. monitors. nec. 19goods. monitors. samsunggoods. monitors. samsung. 15goods. monitors. samsung. 17goods. monitors. samsung. 19goods. monitors. viewsonicgoods. monitors. viewsonic. 17goods. monitors. viewsonic. 19goods. printersgoods. printers. hpgoods. printers. hp. incgoods. printers. hp. lasergoods. printers. samsunggoods. printers. samsung. laser

…………17……19…………samsung………laser

…………Мониторы 17”……Мониторы 19”…………Товары марки Samsung………Лазерные принтеры

Наследование по вхождению, множественное:

Page 9: Вебинар Томулевича materialized.path 2

9

PostgeSQL:

Наследование по вхождению

MySQL:

SELECT t.*, i.* FROM mp_tree AS t JOIN items AS i ON i.id = SUBSTRING_INDEX(t.path, '.', -1);

SELECT t.*, i.* FROM mp_tree AS t JOIN items AS i ON i.id = substring(t.path FROM E'([^\.]+)$')::integer;

Привязка концевых ключей:

Привязка по ключам:

SELECT t.*, i.* FROM items AS i JOIN mp_tree AS t ON t.path LIKE i.id || '.%' OR t.path LIKE '%.' || i.id OR t.path LIKE '%.' || i.id || '.%' WHERE i.id = 2;

Page 10: Вебинар Томулевича materialized.path 2

10

PostgeSQL:

Наследование по вхождению

CREATE OR REPLACE FUNCTION "public"."mp_path_to_array" ( "path" text)RETURNS integer [] AS$body$DECLARE path_array INTEGER[]; item_alias TEXT;BEGIN FOR item_alias IN SELECT regexp_split_to_table(path, E'\\.') LOOP path_array := array_append(path_array, item_alias::integer); END LOOP; RETURN path_array;END;$body$LANGUAGE 'plpgsql';

SELECT i.* FROM items AS i WHERE i.id = ANY (mp_path_to_array('1.2.3.4.5'));

Привязка по ключам:

Page 11: Вебинар Томулевича materialized.path 2

11

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

path TEXT

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

id INTEGER…

tree items

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

path TEXTtid INTEGER

id INTEGER…

tree treesWHERE

FUNCTIONS

Page 12: Вебинар Томулевича materialized.path 2

12

Использование Materialized Path

Задачи:

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

Page 13: Вебинар Томулевича materialized.path 2

13

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

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

SELECT * FROM mp_tree WHERE path = SUBSTRING([path], 1, CHAR_LENGTH([path]) - CHAR_LENGTH(SUBSTRING_INDEX([path], '.', -1)) - 1);

MySQL:

SELECT * FROM mp_tree WHERE path = substring([path] FROM E'^(.+[^\.])\.[^\.]+$');

PostgreSQL:

SELECT * FROM mp_tree WHERE path = subpath([path], 0, -1);

PostgreSQL (ltree):

Использование Materialized Path

Page 14: Вебинар Томулевича materialized.path 2

14

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

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

SELECT * FROM mp_tree WHERE [path] = SUBSTRING(path, 1, CHAR_LENGTH(path) - CHAR_LENGTH(SUBSTRING_INDEX(path, '.', -1)) - 1);

MySQL:

SELECT * FROM mp_tree WHERE [path] = substring(path FROM E'^(.+[^\.])\.[^\.]+$');

PostgreSQL:

SELECT * FROM mp_tree WHERE path ~ '[path].*{1}';

PostgreSQL (ltree):

Использование Materialized Path

Page 15: Вебинар Томулевича materialized.path 2

15

Получение родительской ветви:

Общий:

SELECT t.* FROM mp_tree AS t WHERE [path] LIKE t.path || '.%‘ ORDER BY t.path;

Использование Materialized Path

PostgreSQL (ltree):

SELECT t.* FROM mp_tree AS t WHERE t.path <@ [path] AND t.path <> [path] ORDER BY t.path;

Page 16: Вебинар Томулевича materialized.path 2

16

Получение дочерней ветви:

Общий:

SELECT t.* FROM mp_tree AS t WHERE t.path LIKE '[path].%‘ ORDER BY t.path;

Использование Materialized Path

PostgreSQL (ltree):

SELECT t.* FROM mp_tree AS t WHERE t.path ~ '[path].*' ORDER BY t.path;

Page 17: Вебинар Томулевича materialized.path 2

17

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

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

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

• Изменение path узла;• Изменение path подчиненных узлов;

Page 18: Вебинар Томулевича materialized.path 2

18

Контроль подчиненности:

Триггер PostgreSQL:

CREATE OR REPLACE FUNCTION "public"."mp_tree_update_trigger" () RETURNS trigger AS$body$DECLARE tmp_path TEXT;BEGIN-- Если произошло изменение родителя узла IF NEW.path <> OLD.path THEN-- Проверяем, что бы не поставить родителем потомка IF NEW.path LIKE OLD.path || ‘.%’ THEN RAISE NOTICE 'Нельзя ставить потомком родителя!'; NEW.path := OLD.path; END IF;-- Проверяем, что родитель есть IF NEW.path LIKE '%.%‘ THEN SELECT t.path INTO tmp_path FROM mp_tree AS t WHERE t.path = substring(NEW.path FROM E'^(.+[^\.])\.[^\.]+$'); IF tmp_path IS NULL THEN RAISE NOTICE 'Нельзя ставить несуществующего родителя!'; NEW.path := OLD.path; END IF; END IF; END IF; RETURN NEW;END;$body$LANGUAGE 'plpgsql';

CREATE TRIGGER "mp_tree_update" BEFORE UPDATE ON "public"."mp_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."mp_tree_update_trigger"();

Управление Materialized Path

Page 19: Вебинар Томулевича materialized.path 2

19

Контроль подчиненности:

Триггер PostgreSQL:

CREATE OR REPLACE FUNCTION "public"."mp_tree_update_trigger" () RETURNS trigger AS$body$DECLARE tmp_path TEXT;BEGIN-- Если произошло изменение родителя узла IF NEW.path <> OLD.path THEN-- Проверяем, что бы не поставить родителем потомка IF NEW.path LIKE OLD.path || ‘.%’ THEN RAISE NOTICE 'Нельзя ставить потомком родителя!'; NEW.path := OLD.path; END IF;-- Проверяем, что родитель есть IF NEW.path LIKE '%.%‘ THEN SELECT t.path INTO tmp_path FROM mp_tree AS t WHERE t.path = substring(NEW.path FROM E'^(.+[^\.])\.[^\.]+$'); IF tmp_path IS NULL THEN RAISE NOTICE 'Нельзя ставить несуществующего родителя!'; NEW.path := OLD.path; END IF; END IF; END IF; RETURN NEW;END;$body$LANGUAGE 'plpgsql';

CREATE TRIGGER "mp_tree_update" BEFORE UPDATE ON "public"."mp_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."mp_tree_update_trigger"();

Управление Materialized Path

Page 20: Вебинар Томулевича materialized.path 2

20

Управление Materialized PathДенормализация:

Триггер INSERT PostgreSQL (ltree):

CREATE OR REPLACE FUNCTION "public"."_mp_tree_insert_before_trigger" ( ) RETURNS trigger AS $body$ DECLARE new_path ltree; BEGIN IF NEW.path IS NOT NULL THEN --- передан, какой-то материализованный путь ------- обрезаем в материализованном пути id вставляемого узла, если он есть new_path := CASE WHEN NEW.id::text = subpath(NEW.path, -1, 1)::text THEN subpath(NEW.path, 0, -1) ELSE NEW.path END; ------- проверяем существование родителя SELECT mp.path INTO new_path FROM mp_tree AS mp WHERE mp.path = new_path OR mp.id::text = new_path::text; ------- родителя не нашли IF new_path IS NULL OR new_path = '' THEN NEW.path := NEW.id::text; ELSE -- родителя нашли NEW.path := new_path || NEW.id::text; END IF; ELSE NEW.path := NEW.id::text; END IF; RETURN NEW; END; $body$ LANGUAGE 'plpgsql';

CREATE TRIGGER “mp_tree_insert_before" BEFORE INSERT ON "public"."mp_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."_mp_tree_insert_before_trigger"();

Page 21: Вебинар Томулевича materialized.path 2

21

Управление Materialized PathДенормализация:

Триггер UPDATE PostgreSQL (ltree):

CREATE OR REPLACE FUNCTION "public"."_mp_tree_update_after_trigger" () RETURNS trigger AS$body$DECLARE tid INTEGER; BEGIN--- Принудительно запрещаем изменять ID NEW.id := OLD.id;--- Приводим NULL значение материализованного пути к ID NEW.path := CASE WHEN NEW.path IS NULL THEN NEW.id::text ELSE NEW.path::text END;--- Есть ли у нас изменения материализованного пути IF NEW.path <> OLD.path THEN------- Проверяем что новое значение материализованного пути------- лежит не пределах подчинения и что на его конце ID IF NEW.path ~ ('*.' || NEW.id::text || '.*{1,}')::lquery OR NEW.path ~ ('*.!' || NEW.id::text)::lquery THEN RAISE EXCEPTION 'Bad path!'; END IF;------- Если уровень больше 1 то стоит проверить родителя IF nlevel(NEW.path) > 1 THEN SELECT m.id INTO tid FROM mp_tree AS m WHERE m.path = subpath(NEW.path, 0, nlevel(NEW.path) - 1); IF tid IS NULL THEN RAISE EXCEPTION 'Bad parent!'; END IF; END IF;------- Обновляем детей узла следующего уровня UPDATE mp_tree SET path = NEW.path || id::text WHERE path ~ (OLD.path::text || '.*{1}')::lquery; END IF; RETURN NEW;END;$body$LANGUAGE 'plpgsql'; CREATE TRIGGER “mp_tree_update_after" AFTER UPDATE ON "public"."mp_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."_mp_tree_update_after_trigger"();

Page 22: Вебинар Томулевича materialized.path 2

22

Управление Materialized PathДенормализация:

Триггер DELETE PostgreSQL (ltree):

CREATE OR REPLACE FUNCTION "public"."_mp_tree_delete_after_trigger" () RETURNS trigger AS $body$BEGIN DELETE FROM mp_tree WHERE path ~ (OLD.path::text || '.*{1}')::lquery; RETURN OLD;END;$body$LANGUAGE 'plpgsql'; 

CREATE TRIGGER "mp_tree_delete_after" AFTER DELETE ON "public"."mp_tree" FOR EACH ROW EXECUTE PROCEDURE "public"."_mp_tree_delete_after_trigger"();  

Page 23: Вебинар Томулевича materialized.path 2

23

Вопросы?

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

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

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