F.28. ltree — иерархический древовидный тип данных#
F.28. ltree — иерархический древовидный тип данных #
Этот модуль реализует тип данных ltree для представления меток данных, хранящихся в иерархической структуре, похожей на дерево.
Предоставляются обширные возможности для поиска по деревьям меток.
Этот модуль считается "доверенным", то есть его можно установить
недоступным пользователям, у которых есть привилегия CREATE
в текущей базе данных.
F.28.1. Определения #
A метка представляет собой последовательность буквенно-цифровых символов,
подчеркиваний и дефисов. Допустимые диапазоны буквенно-цифровых символов
зависят от локали базы данных. Например, в локали C разрешены символы
A-Za-z0-9_-.
Метки должны быть не длиннее 1000 символов.
Примеры: 42, Personal_Services
Метка пути (label path) - это последовательность нуля или более меток, разделенных точками, например L1.L2.L3, представляющая путь от корня иерархического дерева к определенному узлу. Длина метки пути не может превышать 65535 меток.
Пример: Top.Countries.Europe.Russia
Модуль ltree предоставляет несколько типов данных:
ltreeхранит путь метки.lqueryпредставляет собой шаблон, похожий на регулярное выражение, для сопоставления значенийltree. Простое слово соответствует этой метке внутри пути. Символ звездочки (*) соответствует нулю или более меткам. Они могут быть объединены точками, чтобы образовать шаблон, который должен соответствовать всему пути метки. Например:foo Соответствует точному пути метки
foo*.foo.* Соответствует любому пути метки, содержащему меткуfoo*.foo Соответствует любому пути метки, последняя метка которого -fooИ звездочки, и простые слова могут быть квантифицированы, чтобы ограничить количество меток, которые они могут соответствовать:
*{n} Соответствует точноnметкам *{n,} Соответствует по крайней мереnметкам *{n,m} Соответствует не менееn, но не болееmметок *{,m} Соответствует не более чемmметкам — то же самое, что и*{0,m} foo{n,m} Соответствует не менееn, но не болееmвхожденийfoofoo{,} Соответствует любому количеству вхожденийfoo, включая нольВ отсутствие явного квантификатора, значение по умолчанию для символа звездочки состоит в том, чтобы соответствовать любому количеству меток (то есть,
{,}), в то время как значение по умолчанию для элемента без звездочки состоит в том, чтобы соответствовать ровно один раз (то есть,{1}).Есть несколько модификаторов, которые можно добавить в конец элемента
lqueryбез звездочки, чтобы он соответствовал не только точному совпадению:@ Сопоставление без учета регистра, например
a@сопоставляется сA* Соответствует любой метке с этим префиксом, напримерfoo*соответствуетfoobar% Соответствие начальным словам, разделенным подчеркиваниемПоведение символа
%немного сложное. Он пытается сопоставить слова, а не всю метку целиком. Например,foo_bar%сопоставляется сfoo_bar_baz, но не сfoo_barbaz. Если символ%комбинируется с символом*, префиксное сопоставление применяется к каждому слову отдельно. Например,foo_bar%*сопоставляется сfoo1_bar2_baz, но не сfoo1_br2_baz.Также, вы можете написать несколько, возможно измененных, элементов, не содержащих звездочку, разделенных символом
|(ИЛИ), чтобы соответствовать любому из этих элементов, и вы можете поставить символ!(НЕ) в начале группы, не содержащей звездочку, чтобы соответствовать любой метке, которая не соответствует ни одной из альтернатив. Квантификатор, если есть, ставится в конце группы; он означает некоторое количество совпадений для всей группы (то есть некоторое количество меток, соответствующих или не соответствующих любой из альтернатив).Вот аннотированный пример
lquery:Top.*{0,2}.sport*@.!football|tennis{1,}.Russ*|Spain a. b. c. d. e.Этот запрос будет соответствовать любому пути метки, который:
начинается с метки
Topи следующее имеет от нуля до двух меток перед
метка, начинающаяся с регистронезависимого префикса
sportзатем имеет одну или несколько меток, ни одна из которых не соответствует
footballилиtennisа затем заканчивается меткой, начинающейся с
Russили точно соответствующейSpain.
ltxtqueryпредставляет собой шаблон для поиска значенийltreeсхожих с полнотекстовым поиском. Значениеltxtqueryсодержит слова, возможно с модификаторами@,*,%в конце; модификаторы имеют те же значения, что и вlquery. Слова могут быть объединены с помощью&(И),|(ИЛИ),!(НЕ) и скобок. Основное отличие отlqueryзаключается в том, чтоltxtqueryсопоставляет слова независимо от их положения в пути метки.Вот пример
ltxtquery:Europe & Russia*@ & !Transportation
Это будет соответствовать путям, содержащим метку
Europeи любую метку, начинающуюся сRussia(без учета регистра), но не путям, содержащим меткуTransportation. Местоположение этих слов внутри пути не имеет значения. Кроме того, при использовании%, слово может соответствовать любому подчеркиванию-разделенному слову внутри метки, независимо от позиции.
Примечание: ltxtquery позволяет пробелы между символами, но ltree и lquery - нет.
F.28.2. Операторы и функции #
Тип ltree имеет обычные операторы сравнения
=, <>,
<, >, <=, >=.
Сравнение сортируется в порядке обхода дерева, с детьми
узла, отсортированными по тексту метки. Кроме того, доступны специализированные
операторы, показанные в Таблица F.13.
Таблица F.13. ltree Операторы
Оператор Описание |
|---|
Является ли левый аргумент предком правого (или равным)? |
Является ли левый аргумент наследником правого (или равным)? |
Соответствует ли |
Соответствует ли |
Соответствует ли |
Соединяет пути |
Преобразует текст в |
Содержит ли массив предка типа |
Содержит ли массив наследника |
Содержит ли массив какой-либо путь, соответствующий |
Содержит ли массив |
Содержит ли массив какой-либо путь, соответствующий |
Возвращает первый элемент массива, который является предком типа |
Возвращает первый элемент массива, который является наследником |
Возвращает первый элемент массива, соответствующий |
Возвращает первый элемент массива, соответствующий |
Операторы <@, @>,
@ и ~ имеют аналоги
^<@, ^@>, ^@,
^~, которые отличаются только тем, что они не используют
индексы. Они полезны только для тестирования.
Все доступные функции показаны в Таблица F.14.
Таблица F.14. ltree Функции
F.28.3. Индексы #
ltree поддерживает несколько типов индексов, которые могут ускорить указанные операторы:
Индекс B-дерева над
lдерево:<,<=,=,>=,>Индекс GiST над
ltree(gist_ltree_opsopclass):<,<=,=,>=,>,@>,<@,@,~,?gist_ltree_opsGiST opclass приближает набор меток пути как битовую подпись. Его необязательный целочисленный параметрsiglenопределяет длину подписи в байтах. Стандартная длина подписи составляет 8 байт. Длина должна быть положительным кратнымintвыравнивания (4 байта на большинстве машин) до 2024. Более длинные подписи приводят к более точному поиску (сканированию меньшей части индекса и меньшего количества страниц кучи), но за счет увеличения размера индекса.Пример создания такого индекса с длиной сигнатуры по умолчанию в 8 байт:
CREATE INDEX path_gist_idx ON test USING GIST (path);
Пример создания такого индекса с длиной сигнатуры 100 байтов:
CREATE INDEX path_gist_idx ON test USING GIST (path gist_ltree_ops(siglen=100));
Индекс GiST над
ltree[](gist__ltree_opsopclass):ltree[] <@ ltree,ltree @> ltree[],@,~,?gist__ltree_opsОператорная классификация GiST работает аналогичноgist_ltree_opsи также принимает длину сигнатуры в качестве параметра. Значение по умолчанию дляsiglenвgist__ltree_opsсоставляет 28 байт.Пример создания такого индекса с длиной сигнатуры по умолчанию в 28 байтов:
CREATE INDEX path_gist_idx ON test USING GIST (array_path);
Пример создания такого индекса с длиной сигнатуры 100 байтов:
CREATE INDEX path_gist_idx ON test USING GIST (array_path gist__ltree_ops(siglen=100));
Примечание: Этот тип индекса является потерянным.
F.28.4. Пример #
Этот пример использует следующие данные (также доступные в файле
contrib/ltree/ltreetest.sql в исходном дистрибутиве):
CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
INSERT INTO test VALUES ('Top.Hobbies');
INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
INSERT INTO test VALUES ('Top.Collections');
INSERT INTO test VALUES ('Top.Collections.Pictures');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
CREATE INDEX path_gist_idx ON test USING GIST (path);
CREATE INDEX path_idx ON test USING BTREE (path);
Сейчас у нас есть таблица test, заполненная данными, описывающими иерархию, показанную ниже:
Вверх
/ | \
Наука Хобби Коллекции
/ | \
Астрономия Любители_астрономии Картинки
/ \ |
Астрофизика Космология Астрономия
/ | \
Галактики Звезды Космонавтыможно использовать наследование:
ltreetest=> SELECT path FROM test WHERE path <@ 'Top.Science';
path
------------------------------------
Top.Science
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(4 rows)
Вот несколько примеров сопоставления пути:
ltreetest=> SELECT path FROM test WHERE path ~ '*.Astronomy.*';
path
-----------------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
Top.Collections.Pictures.Astronomy
Top.Collections.Pictures.Astronomy.Stars
Top.Collections.Pictures.Astronomy.Galaxies
Top.Collections.Pictures.Astronomy.Astronauts
(7 rows)
ltreetest=> SELECT path FROM test WHERE path ~ '*.!pictures@.Astronomy.*';
path
------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(3 rows)
Вот несколько примеров полнотекстового поиска:
ltreetest=> SELECT path FROM test WHERE path @ 'Astro*% & !pictures@';
path
------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
Top.Hobbies.Amateurs_Astronomy
(4 rows)
ltreetest=> SELECT path FROM test WHERE path @ 'Astro* & !pictures@';
path
------------------------------------
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
(3 rows)
Построение пути с использованием функций:
ltreetest=> SELECT subpath(path,0,2)||'Space'||subpath(path,2) FROM test WHERE path <@ 'Top.Science.Astronomy';
?column?
------------------------------------------
Top.Science.Space.Astronomy
Top.Science.Space.Astronomy.Astrophysics
Top.Science.Space.Astronomy.Cosmology
(3 rows)
можно упростить это, создав SQL-функцию, которая вставляет метку в указанную позицию в пути:
CREATE FUNCTION ins_label(ltree, int, text) RETURNS ltree
AS 'select subpath($1,0,$2) || $3 || subpath($1,$2);'
LANGUAGE SQL IMMUTABLE;
ltreetest=> SELECT ins_label(path,2,'Space') FROM test WHERE path <@ 'Top.Science.Astronomy';
ins_label
------------------------------------------
Top.Science.Space.Astronomy
Top.Science.Space.Astronomy.Astrophysics
Top.Science.Space.Astronomy.Cosmology
(3 rows)
F.28.5. Преобразования #
Расширение ltree_plpython3u реализует преобразования для типа ltree для PL/Python. Если оно установлено и указано при создании функции, значения типа ltree отображаются на списки Python. (Обратное в настоящее время не поддерживается, однако).
Предостережение
Сильно рекомендуется установить расширение transform в той же схеме, что и ltree. В противном случае, при установке могут возникнуть проблемы с безопасностью, если схема расширения transform содержит объекты, определенные вредоносным пользователем.
F.28.6. Авторы #
Всю работу выполнили Федор Сигаев (<teodor@stack.net>) и Олег Бартунов (<oleg@sai.msu.su>). Дополнительную информацию можно найти на странице http://www.sai.msu.su/~megera/postgres/gist/. Авторы хотели бы поблагодарить Евгения Родичева за полезные обсуждения. Комментарии и сообщения об ошибках приветствуются.