Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

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 个字符。

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

    另外,您可以用 |(或)分隔数个可能有修改的非星号项,以匹配其中任何项,并且可以在非星号组的开头放置 !(非),以匹配不匹配任何选项的任何标签。如果有的话,量词位于组的末尾;它表示对整个组进行的匹配次数(即,匹配或不匹配任何选项的标签数)。

    以下是 lquery 的注释示例:

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

    此查询将匹配任何标签路径:

    1. 其开头是标签 Top

    2. 然后在后面有 0 到 2 个标签

    3. 在以不区分大小写的前缀 sport 开头的标签之前

    4. 然后有一个或更多标签,其中没有一个匹配 footballtennis

    5. 并且最后以以 Russ 开头的标签或完全匹配 Spain 结尾。

  • ltxtquery 表示用于匹配 ltree 值的类似全文搜索的模式。ltxtquery 值包含单词,其末尾可能带 @*% 修饰符;修饰符的含义与 lquery 中相同。单词可以与 &(与)、|(或)、!(非)和圆括号结合使用。与 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

从位置 start 到位置 end-1(从 0 开始计数)返回 ltree 的子路径。

subltree('Top.Child1.Child2', 1, 2)Child1

subpath ( ltree, offset integer, len integer ) → ltree

从位置 offset(长度为 len)开始返回 ltree 的子路径。如果 offset 为负,则从此位置开始的子路径距路径的末尾很远。如果 len 为负,则从路径的末尾留下那么多标签。

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

subpath ( ltree, offset integer ) → ltree

从位置 offset 开始返回 ltree 的子路径,一直延伸到路径末尾。如果 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_ltree_ops 运算符类) 的 GiST 索引:<<==>=>@><@@~?

    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__ltree_ops 运算符类) 的 GiST 索引: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 列表。(然而,目前不支持逆向映射。)

警告

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

F.22.6. 作者 #

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