PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

F.22. ltree — 层次树状数据类型 #

F.22.1. 定义
F.22.2. 操作符与函数
F.22.3. 索引
F.22.4. 示例
F.22.5. 转换
F.22.6. 作者

该模块实现数据类型 ltree,用于表示存储在层次树状结构中的数据的标签。它提供了丰富的在标签树中搜索的设施。

该模块被视为“可信”模块,也就是说,拥有当前数据库 CREATE 权限的非超级用户也可以安装它。

F.22.1. 定义 #

标签是字母数字字符、下划线与连字符组成的序列。有效的字母数字字符范围取决于数据库区域设置。例如,在 C 区域设置下,允许字符 A-Za-z0-9_-。标签长度不得超过 1000 个字符。

示例:42、Personal_Services

标签路径是由零个或多个以点分隔的标签组成的序列,例如 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 次出现的 foo
    foo{,}      匹配任意次数(包括零次)出现的 foo
    

    在没有任何显式量词的情况下,星号的默认行为是匹配任意数量的标签(即 {,}),而非星号条目的默认行为是精确匹配一次(即 {1})。

    非星号 lquery 条目的末尾可以放置若干修饰符,使其匹配范围超出精确匹配:

    @           不区分大小写匹配,例如 a@ 匹配 A
    *           匹配任何具有该前缀的标签,例如 foo* 匹配 foobar
    %           匹配最初的以下划线分隔的单词
    

    % 的行为有点复杂。它尝试匹配单词而非整个标签。例如 foo_bar% 匹配 foo_bar_baz 但不匹配 foo_barbaz。与 * 组合时,前缀匹配分别应用于每个单词,例如 foo_bar%* 匹配 foo1_bar2_baz 但不匹配 foo1_br2_baz。

    此外,你可以写出多个可能带修饰符的非星号条目,用 |(OR)分隔以匹配其中任意一个;还可以在非星号组的开头放置 !(NOT),以匹配不与任何候选项匹配的标签。量词(如果有)放在组的末尾;它表示该组整体上的匹配次数(即匹配或不匹配任何候选项的标签数量)。

    下面是一个带注释的 lquery 示例:

    Top.*{0,2}.sport*@.!football|tennis{1,}.Russ*|Spain
    a.  b.     c.      d.                   e.
    

    该查询将匹配满足以下条件的任何标签路径:

    1. 以标签 Top 开头

    2. 并且接下来有零到两个标签,然后是

    3. 一个以大小写不敏感前缀 sport 开头的标签,

    4. 接着是一个或多个标签,它们都不匹配 football 或 tennis,

    5. 最后以一个以 Russ 开头的标签或精确匹配 Spain 的标签结尾。

  • ltxtquery 表示一个用于匹配 ltree 值的类全文搜索模式。ltxtquery 值包含单词,末尾可带修饰符 @、*、%;这些修饰符的含义与 lquery 中的相同。单词可以用 &(AND)、|(OR)、!(NOT)与圆括号组合。与 lquery 的关键区别在于,ltxtquery 匹配单词时不考虑它们在标签路径中的位置。

    下面是一个 ltxtquery 示例:

    Europe & Russia*@ & !Transportation
    

    它将匹配包含标签 Europe 与任何以 Russia(大小写不敏感)开头的标签,但不包含标签 Transportation 的路径。这些词在路径中的位置无关紧要。另外,使用 % 时,单词可以匹配标签内任何以下划线分隔的单词,与位置无关。

注意:ltxtquery 允许符号之间有空白,而 ltree 与 lquery 不允许。

F.22.2. 操作符与函数 #

类型 ltree 具有常规比较操作符 =、<>、 <、>、<=、>=。比较按树遍历的顺序排序,节点的子节点按标签文本排序。此外,还可以使用 Table F.12 所示的专用操作符。

Table F.12. ltree 操作符

操作符

描述

ltree @> ltree → boolean

左参数是否为右参数的祖先(或相等)?

ltree <@ ltree → boolean

左参数是否为右参数的后代(或相等)?

ltree ~ lquery → boolean

lquery ~ ltree → boolean

ltree 是否匹配 lquery?

ltree ? lquery[] → boolean

lquery[] ? ltree → boolean

ltree 是否匹配数组中的某个 lquery?

ltree @ ltxtquery → boolean

ltxtquery @ ltree → boolean

ltree 是否匹配 ltxtquery?

ltree || ltree → ltree

连接 ltree 路径。

ltree || text → ltree

text || ltree → ltree

将文本转换为 ltree 并连接。

ltree[] @> ltree → boolean

ltree <@ ltree[] → boolean

数组是否包含 ltree 的某个祖先?

ltree[] <@ ltree → boolean

ltree @> ltree[] → boolean

数组是否包含 ltree 的某个后代?

ltree[] ~ lquery → boolean

lquery ~ ltree[] → boolean

数组是否包含任何匹配 lquery 的路径?

ltree[] ? lquery[] → boolean

lquery[] ? ltree[] → boolean

ltree 数组是否包含任何匹配某个 lquery 的路径?

ltree[] @ ltxtquery → boolean

ltxtquery @ ltree[] → boolean

数组是否包含任何匹配 ltxtquery 的路径?

ltree[] ?@> ltree → ltree

返回数组中第一个作为 ltree 祖先的条目;如果没有则返回 NULL。

ltree[] ?<@ ltree → ltree

返回数组中第一个作为 ltree 后代的条目;如果没有则返回 NULL。

ltree[] ?~ lquery → ltree

返回数组中第一个匹配 lquery 的条目;如果没有则返回 NULL。

ltree[] ?@ ltxtquery → ltree

返回数组中第一个匹配 ltxtquery 的条目;如果没有则返回 NULL。


操作符 <@、@>、@ 与 ~ 有对应的 ^<@、^@>、^@、^~,它们除了不使用索引之外完全相同。这些变体只对测试有用。

可用的函数见 Table F.13。

Table F.13. 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


F.22.3. 索引 #

ltree 支持多种可以加速相应操作符的索引类型:

  • ltree 上的 B 树索引: <、<=、=、 >=、>

  • ltree 上的哈希索引: =

  • ltree 上的 GiST 索引(gist_ltree_ops 操作符类): <、<=、=、 >=、>、 @>、<@、 @、~、?

    gist_ltree_ops GiST 操作符类将一组路径标签近似为位图签名。其可选整数参数 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));
    
  • ltree[] 上的 GiST 索引(gist__ltree_ops 操作符类): ltree[] <@ ltree、ltree @> ltree[]、 @、~、?

    gist__ltree_ops GiST 操作符类的工作方式与 gist_ltree_ops 类似,同样以签名长度作为参数。gist__ltree_ops 中 siglen 的默认值为 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.22.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);
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)

F.22.5. 转换 #

ltree_plpython3u 扩展为 PL/Python 实现了 ltree 类型的转换。如果在创建函数时安装并指定了它,ltree 值会被映射为 Python 列表。(目前不支持反向转换。)

F.22.6. 作者 #

所有工作由 Teodor Sigaev () 与 Oleg Bartunov () 完成。更多信息请参阅 http://www.sai.msu.su/~megera/postgres/gist/。作者感谢 Eugeny Rodichev 的有益讨论。欢迎提出意见与缺陷报告。