2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 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. 定義 #

一個 標籤 是由字母數字字元、下劃線和連字元組成的序列。有效的字母數字字元範圍取決於資料庫的 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 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. 接著有零到兩個標籤,然後是

    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.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 支援幾種型別的索引,可以加速指示的運算子:

  • B-tree 索引,適用於 ltree<, <=, =, >=, >

  • Hash 索引,適用於 ltree=

  • GiST 索引,適用於 ltreegist_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_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 ~ '*.!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.22.5. 轉換 #

ltree_plpython3u 擴充套件為 PL/Python 實現 ltree 型別提供了轉換。如果安裝並指定了此擴充套件,則 ltree 值將被對映到 Python 列表。(反向轉換目前尚不支援。)

F.22.6. 作者 #

所有工作由 Teodor Sigaev()和 Oleg Bartunov()完成。有關更多資訊,請參閱 http://www.sai.msu.su/~megera/postgres/gist/。作者感謝 Eugeny Rodichev 的有益討論。歡迎提供反饋和錯誤報告。

提交更正

如果您在文件中發現任何不正確、不符合您對特定功能的實際體驗或需要進一步澄清的內容,請使用 此表單 報告文件問題。