本模块实现了一种名为 ltree
的数据类型,用于表示存储在分层树状结构中的数据的标签。提供了丰富的设施来搜索标签树。
此模块被认为是“受信任的”,这意味着非超级用户也可以在其拥有的数据库上安装它,前提是他们具有 CREATE
权限。
一个 标签 是由字母数字字符、下划线和连字符组成的序列。有效的字母数字字符范围取决于数据库的 locale。例如,在 C locale 中,允许使用字符 A-Za-z0-9_-
。标签的最大长度为 1000 个字符。
示例: 42
, Personal_Services
一个 标签路径 是由零个或多个由点分隔的标签组成的序列,例如 L1.L2.L3
,它代表了从分层树的根到特定节点的路径。标签路径的长度不能超过 65535 个标签。
示例: Top.Countries.Europe.Russia
ltree
模块提供了几种数据类型:
ltree
存储标签路径。
lquery
表示一种用于匹配 ltree
值的类正则表达式的模式。一个简单的词匹配路径中的该标签。星号(*
)匹配零个或多个标签。这些可以与点连接起来形成一个必须匹配整个标签路径的模式。例如:
foo Match the exact label pathfoo
*.foo.* Match any label path containing the labelfoo
*.foo Match any label path whose last label isfoo
星号和简单词都可以量化以限制它们可以匹配的标签数量。
*{n
} Match exactlyn
labels *{n
,} Match at leastn
labels *{n
,m
} Match at leastn
but not more thanm
labels *{,m
} Match at mostm
labels — same as *{0,m
} foo{n
,m
} Match at leastn
but not more thanm
occurrences offoo
foo{,} Match any number of occurrences offoo
, including zero
如果没有显式量词,星号的默认行为是匹配任意数量的标签(即 {,}
),而非星号项的默认行为是匹配一次(即 {1}
)。
有几个修饰符可以放在非星号 lquery
项的末尾,使其不仅仅匹配精确匹配:
@ Match case-insensitively, for examplea@
matchesA
* Match any label with this prefix, for examplefoo*
matchesfoobar
% 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.
此查询将匹配任何标签路径,该路径:
以标签 Top
开头
接着有零到两个标签,然后是
一个以不区分大小写的 sport
前缀开头的标签
然后有一个或多个标签,这些标签都不匹配 football
或 tennis
最后以一个以 Russ
开头的标签结尾,或者精确匹配 Spain
。
ltxtquery
表示一种用于匹配 ltree
值的类似全文搜索的模式。ltxtquery
值包含词语,可能在其末尾带有修饰符 @
、*
、%
;这些修饰符的含义与 lquery
中的相同。词语可以用 &
(AND)、|
(OR)、!
(NOT)以及括号组合。与 lquery
的关键区别在于 ltxtquery
在匹配词语时不考虑它们在标签路径中的位置。
这是一个 ltxtquery
示例:
Europe & Russia*@ & !Transportation
这将匹配包含标签 Europe
和任何以 Russia
开头的标签(不区分大小写)的路径,但不匹配包含 Transportation
标签的路径。这些词在路径中的位置不重要。另外,当使用 %
时,该词可以匹配标签中的任何下划线分隔的词,而不管其位置。
注意:ltxtquery
允许符号之间存在空格,但 ltree
和 lquery
不允许。
ltree
类型具有常规的比较操作符 =
、<>
、<
、>
、<=
、>=
。比较排序顺序为树遍历顺序,节点的子节点按标签文本排序。此外,还可以使用 表 F.12 中所示的专用操作符。
表 F.12. ltree
操作符
运算符 描述 |
---|
左参数是否为右参数的祖先(或相等)? |
左参数是否为右参数的后代(或相等)? |
|
|
|
连接 |
将文本转换为 |
数组是否包含 |
数组是否包含 |
数组是否包含任何匹配 |
|
数组是否包含任何匹配 |
返回数组中是 |
返回数组中是 |
返回数组中第一个匹配 |
返回数组中第一个匹配 |
操作符 <@
、@>
、@
和 ~
有对应的 ^<@
、^@>
、^@
、^~
,它们的功能相同,只是不使用索引。这些仅用于测试目的。
可用的函数如 表 F.13 所示。
表 F.13. ltree
函数
ltree
支持几种类型的索引,可以加速指示的操作符:
B-tree 索引,适用于 ltree
:<
, <=
, =
, >=
, >
Hash 索引,适用于 ltree
:=
GiST 索引,适用于 ltree
(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));
GiST 索引,适用于 ltree[]
(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));
注意:此索引类型是有损的。
本示例使用以下数据(也包含在源代码发行版的 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
扩展为 PL/Python 实现 ltree
类型提供了转换。如果安装并指定了此扩展,则 ltree
值将被映射到 Python 列表。(反向转换目前尚不支持。)
所有工作由 Teodor Sigaev(<teodor@stack.net>
)和 Oleg Bartunov(<oleg@sai.msu.su>
)完成。有关更多信息,请参阅 http://www.sai.msu.su/~megera/postgres/gist/。作者感谢 Eugeny Rodichev 的有益讨论。欢迎提供反馈和错误报告。
如果您在文档中发现任何不正确、不符合您对特定功能的实际体验或需要进一步澄清的内容,请使用 此表单 报告文档问题。