ltree — иерархический древовидный тип данных
Эта страница переведена при помощи нейросети GigaChat.
Модуль реализует тип данных ltree для представления меток данных, хранящихся в иерархической древовидной структуре. Предоставляются обширные возможности для поиска по деревьям меток.
Этот модуль считается «надежным», то есть его могут устанавливать обычные пользователи с привилегией CREATE на текущей базе данных.
Определения
Метка представляет собой последовательность буквенно-цифровых символов, подчеркиваний и дефисов. Допустимые диапазоны буквенно-цифровых символов зависят от локали базы данных. Например, в локали C разрешены символы A-Za-z0-9_-. Длина метки не должна превышать 1000 символов.
Примеры: 42, Personal_Services
Путь метки – это последовательность из нуля или более меток, разделенных точками, например L1.L2.L3, представляющая путь от корня иерархического дерева к конкретному узлу. Длина пути метки не может превышать 65 535 меток.
Пример: Top.Countries.Europe.Russia
Модуль ltree предоставляет несколько типов данных:
-
ltreeхранит путь метки. -
lqueryпредставляет собой шаблон, похожий на регулярное выражение, для сопоставления значенийltree. Простое слово соответствует этой метке внутри пути. Символ звезды (*) соответствует нулю или более меткам. Их можно объединить точками, чтобы сформировать шаблон, который должен соответствовать всему пути меток. Например:foo Match the exact label path foo
*.foo.* Match any label path containing the label foo
*.foo Match any label path whose last label is fooКак символ звездочки, так и простые слова могут быть количественно определены для ограничения количества меток, которые могут совпадать:
*{n} Match exactly n labels
*{n,} Match at least n labels
*{n,m} Match at least n but not more than m labels
*{,m} Match at most m labels — same as *{0,m}
foo{n,m} Match at least n but not more than m occurrences of foo
foo{,} Match any number of occurrences of foo, including zeroВ отсутствие какого-либо явного квантификатора значение по умолчанию для символа звезды – это соответствие любому количеству меток (т.е.
{,}, а значение по умолчанию для элемента, не являющегося звездой, – это точное совпадение один раз (т.е.{1}).Есть несколько модификаторов, которые можно поставить в конце непустой
lqueryпозиции, чтобы она соответствовала не только точному совпадению:@ Match case-insensitively, for example a@ matches A
* Match any label with this prefix, for example foo* matches foobar
% Match initial underscore-separated wordsПоведение
%нетривиальное. Оно пытается сопоставить слова, а не всю метку целиком. Например,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 нет.
Операторы и функции
Тип ltree имеет обычные операторы сравнения =, <>, <, >, <=, >=. Сравнение сортируется в порядке обхода дерева с сортировкой дочерних элементов узла по тексту метки. Кроме того, доступны специализированные операторы, показанные в таблице ниже.
Операторы ltree:
| Оператор | Описание |
|---|---|
ltree @> ltree → boolean | Является ли левый аргумент предком правого (или равным)? |
ltree <@ ltree → boolean | Является ли левый аргумент потомком правого (или равным)? |
ltree ~ lquery → boolean | Совпадает ли ltree с lquery? |
ltree ? lquery[] → boolean | Совпадает ли ltree с любым lquery в массиве? |
ltree @ ltxtquery → boolean | Совпадает ли ltree с ltxtquery? |
ltree || ltree → ltree | Конкатенирует пути ltree |
ltree || text → ltree | Преобразует текст в ltree и объединяет |
ltree[] @> ltree → boolean | Содержит ли массив предка элемента ltree? |
ltree[] <@ ltree → boolean | Содержит ли массив потомка элемента ltree? |
ltree[] ~ lquery → boolean | Содержит ли массив любой путь, соответствующий элементу lquery? |
ltree[] ? lquery[] → boolean | Содержит ли массив ltree любой путь, соответствующий любому элементу lquery? |
ltree[] @ ltxtquery → boolean | Содержит ли массив какой-либо путь, соответствующий ltxtquery? |
ltree[] ?@> ltree → ltree | Возвращает первый элемент массива, который является предком ltree, или NULL, если нет |
ltree[] ?<@ ltree → ltree | Возвращает первый элемент массива, который является потомком ltree, или NULL, если нет |
ltree[] ?~ lquery → ltree | Возвращает первый элемент массива, соответствующий lquery, или NULL, если нет |
ltree[] ?@ ltxtquery → ltree | Возвращает первый элемент массива, соответствующий ltxtquery, или NULL, если нет |
Операторы <@, @>, @ и ~ имеют аналоги ^<@, ^@>, ^@, ^~, которые одинаковы за исключением того, что они не используют индексы. Они полезны только для целей тестирования.
Доступные функции показаны в таблице ниже.
Функции ltree:
| Функция | Описание | Пример(ы) |
|---|---|---|
subltree ( ltree, start integer, end integer ) → ltree | Возвращает подстроку из ltree от позиции start до позиции end-1 (счет начинается с 0) | subltree('Top.Child1.Child2', 1, 2) → Child1 |
subpath ( ltree, offset integer, len integer ) → ltree | Возвращает подстроку из ltree, начиная с позиции offset, длиной len. Если offset отрицательный, подстрока начинается так далеко от конца пути. Если len отрицательная, оставляет столько меток в конце пути | subpath('Top.Child1.Child2', 0, 2) → Top.Child1 |
subpath ( ltree, offset integer ) → ltree | Возвращает подстроку из ltree, начиная с позиции offset, простирающейся до конца пути. Если offset отрицательный, подстрока начинается так далеко от конца пути | subpath('Top.Child1.Child2', 1) → Child1.Child2 |
nlevel ( ltree ) → integer | Возвращает количество меток в пути | nlevel('Top.Child1.Child2') → 3 |
index ( a ltree, b ltree ) → integer | Возвращает позицию первого вхождения b в a или -1, если не найдено | index('0.1.2.3.5.4.5.6.8.5.6.8', '5.6') → 6 |
index ( a ltree, b ltree, offset integer ) → integer | Возвращает позицию первого вхождения b в a или -1, если не найдено. Поиск начинается с позиции offset; отрицательное значение offset означает начало -offset меток от конца пути | index('0.1.2.3.5.4.5.6.8.5.6.8', '5.6', -4) → 9 |
text2ltree ( text ) → ltree | Приводит text к ltree | |
ltree2text ( ltree ) → text | Приводит ltree к text | |
lca ( ltree [, ltree [, ... ]] ) → ltree | Вычисляет ближайшего общего предка путей (поддерживается до 8 аргументов) | lca('1.2.3', '1.2.3.4.5.6') → 1.2 |
lca ( ltree[] ) → ltree | Вычисляет ближайшего общего предка путей в массиве | lca(array['1.2.3'::ltree,'1.2.3.4']) → 1.2 |
Индексы
ltree поддерживает несколько типов индексов, которые могут ускорить указанные операторы:
-
B-дерево по значениям
ltree:<,<=,=,>=,>. -
Хеш-индекс по значениям
ltree:=. -
GiST по значениям
ltree(класс операторовgist_ltree_ops):<,<=,=,>=,>,@>,<@,@,~,?Класс операций GiST для
gist_ltree_opsаппроксимирует набор меток пути в виде сигнатуры битовой карты. Его необязательный целочисленный параметрsiglenопределяет длину подписи в байтах. Длина подписи по умолчанию составляет 8 байт. Длина должна быть положительным значением до 2024, кратным выравниванию по целым (int) (4 байта на большинстве машин). Более длинные подписи приводят к более точному поиску (сканированию меньшей доли индекса и меньшего количества страниц кучи), за счет увеличения размера индекса.Пример создания такого индекса с длиной подписи по умолчанию 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));Примечание: Индекс этого типа является неточным.
Пример
Этот пример использует следующие данные (также доступны в файле 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);
CREATE INDEX path_hash_idx ON test USING HASH (path);
Теперь существует таблица test, заполненная данными, описывающими иерархию, показанную ниже:
Top
/ | \
Science Hobbies Collections
/ | \
Astronomy Amateurs_Astronomy Pictures
/ \ |
Astrophysics Cosmology Astronomy
/ | \
Galaxies Stars Astronauts
Можно выполнять наследование:
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)
Преобразования
Расширение ltree_plpython3u реализует преобразование для типа ltree для PL/Python. Если он установлен и указан при создании функции, значения ltree отображаются на списки Python (обратный процесс в настоящее время не поддерживается).
Рекомендуется установить расширение преобразования в той же схеме, что и ltree. В противном случае существует опасность нарушения безопасности во время установки, если схема расширения преобразования содержит объекты, определенные враждебным пользователем.