CREATE TABLE object ( id NUMBER(11), parent_id NUMBER(11), type VARCHAR2(16) NOT NULL, name VARCHAR2(255) NOT NULL, CONSTRAINT pk_object PRIMARY KEY (id), CONSTRAINT fk_object_parent FOREIGN KEY (parent_id) REFERENCES object (id) ON DELETE CASCADE ENABLE );
Наполним её какими-нибудь данными:
id | parent_id | type | name ------------------------------------------------------ 1 | NULL | country | Россия 2 | 1 | region | Московская область 3 | 1 | region | Новосибирская область 4 | 2 | city | Москва 5 | 3 | city | Новосибирск
При этом мы можем легко одним запросом получать нужные нам связи:
-- Выбрать все города России SELECT * FROM object WHERE type = 'city' START WITH id = 1 CONNECT BY PRIOR id = parent_id; -- Выбрать страну, в которой находится Новосибирск SELECT * FROM object WHERE type = 'country' START WITH id = 5 CONNECT BY PRIOR parent_id = id;
Однако проблемы появляются, когда записей в таблице становится на столько много, что любой рекурсивный запрос выполняется минуты две, а то и больше. Менять всю архитектуру как-то поздновато… Тут-то нам на помощь и приходит денормализация дерева. В этой статье я расскажу об одном из способов такой денормализации.
Основная идея заключается в использовании материализованного представления, которое хранит связи в более удобном для запросов виде. Для начала добавим пакет objects с функцией get_object_fast_table, которая будет возвращать нам денормализованные связи:
CREATE OR REPLACE PACKAGE objects AS TYPE object_fast_type IS RECORD ( object_id object.id%TYPE, object_name object.name%TYPE, object_type object.type%TYPE, parent_id object.id%TYPE, parent_name object.name%TYPE, parent_type object.type%TYPE, nesting_level NUMBER(20) ); TYPE object_fast_table IS TABLE OF objects.object_fast_type; FUNCTION get_object_fast_table RETURN objects.object_fast_table PIPELINED; END objects; / CREATE OR REPLACE PACKAGE BODY objects AS FUNCTION get_object_fast_table RETURN objects.object_fast_table PIPELINED IS CURSOR objs IS SELECT id FROM object; CURSOR parents(obid object.id%TYPE) IS SELECT obid object_id, obj.name object_name, obj.type object_type, par.id parent_id, par.name parent_name, par.type parent_type, level - 1 nesting_level FROM object par LEFT JOIN object obj ON obj.id = obid START WITH par.id = obid CONNECT BY PRIOR par.parent_id = par.id; BEGIN FOR obj IN objs LOOP FOR object_fast IN parents(obj.id) LOOP PIPE ROW (object_fast); END LOOP; END LOOP; RETURN; END get_object_fast_table; END objects; /
Теперь мы можем использовать денормализованную таблицу в запросах:
SELECT * FROM TABLE(objects.get_object_fast_table());
object_id | object_name | object_type | parent_id | parent_name | parent_type | nesting_level ------------------------------------------------------------------------------------------------------------------ 1 | Россия | country | 1 | Россия | country | 0 2 | Московская область | region | 2 | Московская область | region | 0 2 | Московская область | region | 1 | Россия | country | 1 3 | Новосибирская область | region | 3 | Новосибирская область | region | 0 3 | Новосибирская область | region | 1 | Россия | country | 1 4 | Москва | city | 4 | Москва | city | 0 4 | Москва | city | 2 | Московская область | region | 1 4 | Москва | city | 1 | Россия | country | 2 5 | Новосибирск | city | 5 | Новосибирск | city | 0 5 | Новосибирск | city | 3 | Новосибирская область | region | 1 5 | Новосибирск | city | 1 | Россия | country | 2
Как можно увидеть, в таблице есть связи каждого объекта со всеми его родителями, а nesting_level — это число уровней до родителя. Чтоб постоянно не дёргать эту функцию, сохраним связи в материализованное представление:
CREATE MATERIALIZED VIEW object_fast REFRESH COMPLETE ON DEMAND START WITH trunc(sysdate)+4/24 NEXT (trunc(sysdate)+1)+4/24 AS SELECT rownum id, objs.* FROM TABLE(objects.get_object_fast_table()) objs; ALTER TABLE object_fast ADD CONSTRAINT pk_object_fast PRIMARY KEY (id);
Теперь вышеупомянутые запросы будут выглядеть так:
-- Выбрать все города России SELECT * FROM object_fast WHERE parent_id = 1 AND object_type = 'city'; -- Выбрать страну, в которой находится Новосибирск SELECT * FROM object_fast WHERE object_id = 5 AND parent_type = 'country';
Ну и, по желанию, можно добавить индексы:
CREATE INDEX object_fast_obj_id ON object_fast (object_id); CREATE INDEX object_fast_par_id ON object_fast (parent_id); CREATE INDEX object_fast_obj_type ON object_fast (object_type); CREATE INDEX object_fast_par_type ON object_fast (parent_type); CREATE INDEX object_fast_nesting ON object_fast (nesting_level);
Вот и всё. От себя скажу, что на нашем проекте этот способ дал прирост скорости запросов примерно в 60 раз. Используйте с умом и не забывайте, что полученные данные будут не всегда актуальными. Рекомендую применять этот способ только к редко добавляющимся и удаляющимся объектам. Ну или тогда стоит реализовать оперативное обновление материализованного представления. Нет предела полёту фантазии…
ссылка на оригинал статьи http://habrahabr.ru/post/212847/
Добавить комментарий