2024 年 9 月 26 日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3

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

此模块实现了一个数据类型 ltree 用于表示存储在分层树状结构中的数据的标签。提供了丰富的用于搜索标签树的功能。

此模块被认为是 可信的,也就是说,非超级用户如果对当前数据库具有 CREATE 权限,则可以安装它。

F.22.1. 定义 #

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

示例:42Personal_Services

一个 标签路径 是零个或多个标签的序列,它们用点分隔,例如 L1.L2.L3,表示从分层树的根节点到特定节点的路径。标签路径的长度不能超过 65535 个标签。

示例: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

    此外,您可以用 | (OR) 分隔几个可能修改过的非星号项来匹配这些项中的任何一项,并且您可以在非星号组的开头放置 ! (NOT) 来匹配不匹配任何备选方案的任何标签。量化符(如果有)位于组的末尾;它表示对整个组进行一定数量的匹配(即,匹配或不匹配任何备选方案的标签数量)。

    这是一个 lquery 的带注释示例

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

    此查询将匹配任何以以下标签开头的标签路径

    1. 以标签 Top 开头

    2. 并且接下来在以下标签之前有 0 到 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 允许符号之间存在空格,但 ltreelquery 不允许。

F.22.2. 运算符和函数 #

类型 ltree 具有通常的比较运算符 =<><><=>=。比较按树遍历的顺序进行排序,节点的子节点按标签文本排序。此外,还提供了 表 F.12 中显示的专用运算符。

表 F.12. ltree 运算符

运算符

描述

ltree @> ltreeboolean

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

ltree <@ ltreeboolean

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

ltree ~ lqueryboolean

lquery ~ ltreeboolean

ltree 是否匹配 lquery

ltree ? lquery[]boolean

lquery[] ? ltreeboolean

ltree 是否匹配数组中的任何 lquery

ltree @ ltxtqueryboolean

ltxtquery @ ltreeboolean

ltree 是否匹配 ltxtquery

ltree || ltreeltree

连接 ltree 路径。

ltree || textltree

text || ltreeltree

将文本转换为 ltree 并连接。

ltree[] @> ltreeboolean

ltree <@ ltree[]boolean

数组是否包含 ltree 的祖先?

ltree[] <@ ltreeboolean

ltree @> ltree[]boolean

数组是否包含 ltree 的后代?

ltree[] ~ lqueryboolean

lquery ~ ltree[]boolean

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

ltree[] ? lquery[]boolean

lquery[] ? ltree[]boolean

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

ltree[] @ ltxtqueryboolean

ltxtquery @ ltree[]boolean

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

ltree[] ?@> ltreeltree

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

ltree[] ?<@ ltreeltree

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

ltree[] ?~ lqueryltree

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

ltree[] ?@ ltxtqueryltree

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


操作符 <@@>@~ 具有类似的运算符 ^<@^@>^@^~,它们的功能相同,只是不使用索引。这些运算符仅用于测试目的。

可用的函数在 表 F.13 中列出。

表 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 为负数,则子路径从路径末尾开始 -offset 个标签处。如果 len 为负数,则从路径末尾删除 -len 个标签。

subpath('Top.Child1.Child2', 0, 2)Top.Child1

subpath ( ltree, offset integer ) → ltree

返回 ltree 从位置 offset 开始,扩展到路径末尾的子路径。如果 offset 为负数,则子路径从路径末尾开始 -offset 个标签处。

subpath('Top.Child1.Child2', 1)Child1.Child2

nlevel ( ltree ) → integer

返回路径中标签的数量。

nlevel('Top.Child1.Child2')3

index ( a ltree, b ltree ) → integer

返回 ba 中第一次出现的位置,如果未找到则返回 -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

返回 ba 中第一次出现的位置,如果未找到则返回 -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[] <@ ltreeltree @> ltree[]@~?

    gist__ltree_ops GiST 操作符类与 gist_ltree_ops 工作原理类似,也接受签名长度作为参数。gist__ltree_opssiglen 的默认值为 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 ~ '*[email protected].*';
                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 列表。(反向操作目前不支持。)

警告

强烈建议将转换扩展安装在与 ltree 相同的模式中。否则,如果转换扩展的模式包含由恶意用户定义的对象,则存在安装时的安全隐患。

F.22.6. 作者 #

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

提交更正

如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符或需要进一步说明,请使用 此表格 报告文档问题。