87. 10 Наследование по вхождению Привязка по ключам: PostgeSQL: CREATE OR REPLACE FUNCTION "public"."mp_path_to_array" ( "path" text ) RETURNSinteger [] AS $body$ DECLARE path_arrayINTEGER[]; item_aliasTEXT; BEGIN FORitem_aliasIN SELECT regexp_split_to_table(path, E'.') LOOP path_array := array_append(path_array, item_alias::integer); END LOOP; RETURNpath_array; END; $body$ LANGUAGE'plpgsql'; SELECTi.* FROMitemsASiWHEREi.id = ANY (mp_path_to_array('1.2.3.4.5'));
88. Структуры таблиц Множественное наследование: FUNCTIONS tree items id INTEGER path TEXT … path TEXT tid INTEGER Множество деревьев: WHERE tree trees id INTEGER … 11
100. Управление Materialized Path Контроль подчиненности: Триггер PostgreSQL: CREATE OR REPLACE FUNCTION "public"."mp_tree_update_trigger" () RETURNStriggerAS $body$ DECLARE tmp_pathTEXT; BEGIN -- Если произошло изменение родителя узла IF NEW.path <> OLD.pathTHEN -- Проверяем, что бы не поставить родителем потомка IFNEW.pathLIKE OLD.path || ‘.%’ THEN RAISE NOTICE 'Нельзя ставить потомком родителя!'; NEW.path := OLD.path; END IF; -- Проверяем, что родитель есть IF NEW.pathLIKE '%.%‘ THEN SELECTt.pathINTOtmp_path FROMmp_treeAS t WHEREt.path= substring(NEW.pathFROM E'^(.+[^])[^]+$'); IFtmp_pathIS NULLTHEN 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"(); 18
101. Управление Materialized Path Контроль подчиненности: Триггер PostgreSQL: CREATE OR REPLACE FUNCTION "public"."mp_tree_update_trigger" () RETURNStriggerAS $body$ DECLARE tmp_pathTEXT; BEGIN -- Если произошло изменение родителя узла IF NEW.path <> OLD.pathTHEN -- Проверяем, что бы не поставить родителем потомка IFNEW.pathLIKE OLD.path || ‘.%’ THEN RAISE NOTICE 'Нельзя ставить потомком родителя!'; NEW.path := OLD.path; END IF; -- Проверяем, что родитель есть IF NEW.pathLIKE '%.%‘ THEN SELECTt.pathINTOtmp_path FROMmp_treeAS t WHEREt.path= substring(NEW.pathFROM E'^(.+[^])[^]+$'); IFtmp_pathIS NULLTHEN 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"(); 19
102. Управление Materialized Path Денормализация: Триггер INSERT PostgreSQL(ltree): CREATE OR REPLACE FUNCTION "public"."_mp_tree_insert_before_trigger" ( ) RETURNStrigger AS $body$ DECLARE new_pathltree; BEGIN IFNEW.pathIS NOT NULL THEN --- передан, какой-то материализованный путь ------- обрезаем в материализованном пути id вставляемого узла, если он есть new_path:= CASE WHEN NEW.id::text = subpath(NEW.path, -1, 1)::text THENsubpath(NEW.path, 0, -1) ELSENEW.path END; ------- проверяем существование родителя SELECTmp.pathINTOnew_path FROMmp_treeASmp WHEREmp.path= new_pathOR mp.id::text = new_path::text; ------- родителя не нашли IFnew_pathIS 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; RETURNNEW; 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"(); 20
103. Управление Materialized Path Денормализация: Триггер UPDATE PostgreSQL(ltree): CREATE OR REPLACE FUNCTION "public"."_mp_tree_update_after_trigger" ()RETURNStriggerAS $body$ DECLARE tid INTEGER; BEGIN --- Принудительно запрещаем изменять ID NEW.id := OLD.id; --- Приводим NULL значение материализованного пути к ID NEW.path:= CASE WHEN NEW.pathIS NULL THEN NEW.id::text ELSENEW.path::text END; --- Есть ли у нас изменения материализованного пути IFNEW.path<> OLD.pathTHEN ------- Проверяем что новое значение материализованного пути ------- лежит не пределах подчинения и что на его конце ID IFNEW.path~ ('*.' || NEW.id::text || '.*{1,}')::lqueryOR NEW.path~ ('*.!' || NEW.id::text)::lquery THEN RAISE EXCEPTION 'Bad path!'; END IF; ------- Если уровень больше 1 то стоит проверить родителя IF nlevel(NEW.path) > 1 THEN SELECT m.id INTO tidFROMmp_tree AS m WHEREm.path= subpath(NEW.path, 0, nlevel(NEW.path) - 1); IFtidIS NULL THEN RAISE EXCEPTION 'Bad parent!'; END IF; END IF; ------- Обновляем детей узла следующего уровня UPDATEmp_tree SETpath = NEW.path|| id::text WHEREpath ~ (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"(); 21
104. Управление Materialized Path Денормализация: Триггер DELETE PostgreSQL(ltree): CREATE OR REPLACE FUNCTION "public"."_mp_tree_delete_after_trigger" () RETURNStrigger AS $body$ BEGIN DELETEFROMmp_tree WHEREpath ~ (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"(); 22
105. Вопросы? Статьи по теме: http://doc.prototypes.ru/database/trees/ Сергей Томулевич Rambler, Москва, 2010 Карикатуры: Сергей Корсун 23