ltree
ltree
这个模块实现了一种数据类型ltree用于表示存储在一个层次树状结构中的数据的标签。还提供了在标签树中搜索的扩展功能。
定义
一个标签是一个字母数字字符和下划线的序列(例如,在 C 区域中允许字符A-Za-z0-9_)。标签长度必须少于 256 字节。
例子: 42, Personal_Services
一个标签路径是由点号分隔的零个或者更多个标签的序列,例如L1.L2.L3,它表示一个从层次树的根到一个特定节点的路径。一个标签路径的长度必须小于 65kB,但是最好将它保持在 2kB 以下。
例子: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}相同
在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.Russ*|Spain
a. b. c. d. e.
这个查询将匹配任何这样的标签路径:
开始于标签
Top并且接着具有 0 到 2 个标签
之后是一个开始于大小写无关的前缀
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.13中显示的特殊操作符。
表 F.13. ltree 操作符
| 操作符 | 返回值 | 描述 |
|---|---|---|
ltree @> ltree |
boolean |
左参数是不是右参数的一个祖先(或者相等)? |
ltree <@ ltree |
boolean |
左参数是不是右参数的一个后代(或者相等)? |
ltree ~ lquery |
boolean |
ltree匹配lquery吗? |
lquery ~ ltree |
boolean |
ltree匹配lquery吗? |
ltree ? lquery[] |
boolean |
ltree匹配数组中的任意lquery吗? |
lquery[] ? ltree |
boolean |
ltree匹配数组中的任意lquery吗? |
ltree @ ltxtquery |
boolean |
ltree匹配ltxtquery吗? |
ltxtquery @ ltree |
boolean |
ltree匹配ltxtquery吗? |
ltree | | ltree |
||
ltree | | text |
||
text | | ltree |
||
ltree[] @> ltree |
boolean |
数组是否包含ltree的一个祖先? |
ltree <@ ltree[] |
boolean |
数组是否包含ltree的一个祖先? |
ltree[] <@ ltree |
boolean |
数组是否包含ltree的一个后代? |
ltree @> ltree[] |
boolean |
数组是否包含ltree的一个后代? |
ltree[] ~ lquery |
boolean |
数组是否包含匹配lquery的路径? |
lquery ~ ltree[] |
boolean |
数组是否包含匹配lquery的路径? |
ltree[] ? lquery[] |
boolean |
ltree数组是否包含匹配任意lquery的路径? |
lquery[] ? ltree[] |
boolean |
ltree数组是否包含匹配任意lquery的路径? |
ltree[] @ ltxtquery |
boolean |
数组是否包含匹配ltxtquery的路径? |
ltxtquery @ ltree[] |
boolean |
数组是否包含匹配ltxtquery的路径? |
ltree[] ?@> ltree |
ltree |
是ltree祖先的第一个数组项;如果没有则是 NULL |
ltree[] ?<@ ltree |
ltree |
是ltree祖先的第一个数组项;如果没有则是 NULL |
ltree[] ?~ lquery |
ltree |
匹配lquery的第一个数组项;如果没有则是 NULL |
ltree[] ?@ ltxtquery |
ltree |
匹配lquery的第一个数组项;如果没有则是 NULL |
操作符<@、@>、 @以及~有类似的、 ^<@、^@>、^@、 ^~,只是它们不适用索引。它们只对测试目的有用。
可用的函数在表 F.14中。
表 F.14. ltree 函数
| 函数 | 返回类型 | 描述 | 例子 | 结果 |
|---|---|---|---|---|
subltree(ltree, int start, int end) |
ltree |
ltree的从位置start到位置end-1(从 0 开始计)的子路径 |
subltree('Top.Child1.Child2',1,2) |
Child1 |
subpath(ltree, int offset, int len) |
ltree |
ltree从位置offset开始长度为len的子路径。如果offset为负,则子路径开始于距离路径尾部那么远的位置。如果len为负,则从路径的尾部开始丢掉那么多个标签。 |
subpath('Top.Child1.Child2',0,2) |
Top.Child1 |
subpath(ltree, int offset) |
ltree |
ltree从位置offset开始一直延伸到路径末尾的子路径。如果offset为负,则子路径开始于距离路径尾部那么远的位置。 |
subpath('Top.Child1.Child2',1) |
Child1.Child2 |
nlevel(ltree) |
integer |
路径中标签的数量 | nlevel('Top.Child1.Child2') |
3 |
index(ltree a, ltree b) |
integer |
a中第一次出现b的位置,如果没有找到则为 -1 |
index('0.1.2.3.5.4.5.6.8.5.6.8','5.6') |
6 |
index(ltree a, ltree b, int offset) |
integer |
a中第一次出现b的位置,搜索从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 |
索引
ltree支持一些能加速上述操作符的索引类型:
ltree上的 B-树索引:<、<=、=、>=、>ltree上的 GiST 索引:<、<=、=、>=、>、@>、<@、@、~、?创建这样一个索引的例子:
CREATE INDEX path_gist_idx ON test USING GIST (path);
ltree[]上的 GiST 索引:ltree[] <@ ltree、ltree @> ltree[]、@、~、?创建这样一个索引的例子:
CREATE INDEX path_gist_idx ON test USING GIST (array_path);
注意:这种索引类型是有损的。
例子
这个例子使用下列数据(在源代码发布的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);
现在,我们有一个表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)