受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

F.20. ltree #

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

该模块被认为是受信任的,也就是说,它可以由在当前数据库上具有 CREATE 权限的非超级用户安装。

F.20.1. 定义 #

标签是由字母数字字符和下划线组成的序列(例如,在 C 区域设置下,允许的字符为 A-Za-z0-9_)。标签长度必须少于 256 个字符。

示例:42Personal_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 次、但不超过 mfoo
    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. 接着有一个或多个标签,这些标签都不匹配footballtennis

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

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

    下面是一个ltxtquery示例:

    Europe & Russia*@ & !Transportation
    

    它将匹配包含标签Europe以及任意以Russia开头(不区分大小写)的标签的路径, 但不匹配包含标签Transportation的路径。这些单词在路径中的位置并不重要。 另外,当使用%时,该单词可以匹配标签中任意由下划线分隔的单词,而不考虑其位置。

注意:ltxtquery允许在符号之间出现空白,而ltreelquery不允许。

F.20.2. 操作符和函数 #

类型ltree具有常见的比较操作符 =<><><=>=。 比较时采用树遍历顺序,其中节点的子节点按标签文本排序。此外,还提供了 Table F.12中所示的专用操作符。

Table 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


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

可用函数见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

返回从位置offset开始、长度为 lenltree子路径。如果offset 为负,则子路径从距路径末尾-offset个标签处开始。如果len 为负,则从路径末尾省去那么多个标签。

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

subpath ( ltree, offset integer ) → ltree

返回从位置offset开始、一直延伸到路径末尾的 ltree子路径。如果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.20.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_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.20.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.20.5. 转换 #

有额外的扩展实现了 PL/Python 的ltree类型转换。这些扩展分别叫做ltree_plpythonultree_plpython2ultree_plpython3u(关于 PL/Python 的命名约定请见Section 44.1)。如果安装了这些转换扩展,并在创建函数时指定它们,则ltree值会映射为 Python 列表。(不过,目前还不支持反向映射。)

Caution

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

F.20.6. 作者 #

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

提交更正

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