到目前為止描述的過程允許我們定義新的型別、新的函式和新的運算子。但是,我們還不能為一個新資料型別的列定義索引。要做到這一點,我們必須為新資料型別定義一個運算子類。在本節後面,我們將透過一個例子來說明這個概念:為 B-tree 索引方法建立一個新的運算子類,該類按照絕對值升序儲存和排序複數。
運算子類可以分組到運算子族中,以顯示語義相容類之間的關係。當只涉及一種資料型別時,一個運算子類就足夠了,所以我們將首先關注這種情況,然後再回到運算子族。
運算子類與索引訪問方法相關聯,例如 B-Tree 或 GIN。可以使用 CREATE ACCESS METHOD 定義自定義索引訪問方法。有關詳細資訊,請參閱 第 63 章。
索引方法的例程本身並不直接瞭解索引方法將操作的資料型別。相反,一個運算子類 標識索引方法用於處理特定資料型別所需的操作集。之所以稱為運算子類,是因為它們指定了可以與索引一起使用(即可以轉換為索引掃描限定條件)的WHERE
子句運算子集。運算子類還可以指定一些支援函式,這些函式是索引方法內部操作所必需的,但與任何可與索引一起使用的WHERE
子句運算子都不直接對應。
可以為相同的資料型別和索引方法定義多個運算子類。透過這樣做,可以為單個數據型別定義多個索引語義集。例如,B-tree 索引在其處理的每種資料型別上都需要定義排序順序。對於複數資料型別,可能需要一個 B-tree 運算子類,該類按複數絕對值排序資料,另一個按實部排序,依此類推。通常,其中一個運算子類將被認為是最常用的,並被標記為該資料型別和索引方法的預設運算子類。
相同的運算子類名稱可以用於幾個不同的索引方法(例如,B-tree 和 hash 索引方法都有名為 int4_ops
的運算子類),但每個這樣的類都是獨立的實體,必須單獨定義。
與運算子類相關聯的運算子由“策略號”標識,它們在運算子類的上下文中用於標識每個運算子的語義。例如,B-tree 在鍵上施加嚴格的升序排序,因此像“小於”和“小於等於”這樣的運算子對於 B-tree 來說很重要。由於PostgreSQL允許使用者定義運算子,因此PostgreSQL無法僅憑運算子的名稱(例如,<
或 >=
)來判斷它屬於哪種比較型別。相反,索引方法定義了一組“策略”,可以將其視為廣義運算子。每個運算子類指定了特定資料型別和索引語義解釋的每個策略對應的實際運算子。
B-tree 索引方法定義了五種策略,如表 36.3 所示。
表 36.3. B-Tree 策略
操作 | 策略號 |
---|---|
小於 | 1 |
小於等於 | 2 |
等於 | 3 |
大於等於 | 4 |
大於 | 5 |
Hash 索引只支援相等比較,因此它們只使用一種策略,如表 36.4 所示。
表 36.4. Hash 策略
操作 | 策略號 |
---|---|
等於 | 1 |
GiST 索引更靈活:它們根本沒有固定的策略集。相反,每個特定 GiST 運算子類的“一致性”支援例程會按其喜歡的方式解釋策略號。例如,一些內建的 GiST 索引運算子類索引二維幾何物件,提供了表 36.5 中所示的“R-tree”策略。其中四個是真正的二維測試(重疊、相同、包含、被包含);四個只考慮 X 方向;另外四個提供 Y 方向的相同測試。
表 36.5. GiST 二維“R-tree”策略
操作 | 策略號 |
---|---|
嚴格左側 | 1 |
不向右延伸 | 2 |
重疊 | 3 |
不向左延伸 | 4 |
嚴格右側 | 5 |
相同 | 6 |
包含 | 7 |
被包含 | 8 |
不向上延伸 | 9 |
嚴格下方 | 10 |
嚴格上方 | 11 |
不向下延伸 | 12 |
SP-GiST 索引在靈活性方面與 GiST 索引類似:它們也沒有固定的策略集。相反,每個運算子類的支援例程根據運算子類的定義來解釋策略號。例如,為點內建的運算子類使用的策略號顯示在表 36.6 中。
表 36.6. SP-GiST 點策略
操作 | 策略號 |
---|---|
嚴格左側 | 1 |
嚴格右側 | 5 |
相同 | 6 |
被包含 | 8 |
嚴格下方 | 10 |
嚴格上方 | 11 |
GIN 索引也與 GiST 和 SP-GiST 索引類似,它們也沒有固定的策略集。相反,每個運算子類的支援例程根據運算子類的定義來解釋策略號。例如,為陣列內建的運算子類使用的策略號顯示在表 36.7 中。
表 36.7. GIN 陣列策略
操作 | 策略號 |
---|---|
重疊 | 1 |
包含 | 2 |
被包含 | 3 |
等於 | 4 |
BRIN 索引也與 GiST、SP-GiST 和 GIN 索引類似,它們也沒有固定的策略集。相反,每個運算子類的支援例程根據運算子類的定義來解釋策略號。例如,為Minmax
內建的運算子類使用的策略號顯示在表 36.8 中。
表 36.8. BRIN Minmax 策略
操作 | 策略號 |
---|---|
小於 | 1 |
小於等於 | 2 |
等於 | 3 |
大於等於 | 4 |
大於 | 5 |
請注意,上面列出的所有運算子都返回布林值。實際上,所有被定義為索引方法搜尋運算子的運算子都必須返回boolean
型別,因為它們必須出現在WHERE
子句的頂層才能與索引一起使用。(某些索引訪問方法還支援排序運算子,它們通常不返回布林值;這一特性在第 36.16.7 節中討論。)
策略通常不足以讓系統弄清楚如何使用索引。實際上,索引方法需要額外的支援例程才能工作。例如,B-tree 索引方法必須能夠比較兩個鍵並確定一個鍵是大於、等於還是小於另一個。同樣,hash 索引方法必須能夠為鍵值計算雜湊碼。這些操作不對應於 SQL 命令中使用的運算子;它們是由索引方法內部使用的管理例程。
與策略一樣,運算子類透過將它們分配給索引方法指定的“支援函式號”來標識運算子類需要使用哪些特定函式來扮演給定資料型別和語義解釋的每個角色。
此外,一些 opclasses 允許使用者指定控制其行為的引數。每個內建的索引訪問方法都有一個可選的options
支援例程,它定義了一組 opclass 特定的引數。
B-trees 需要一個比較支援例程,並允許根據運算子類作者的選擇提供四個額外的支援例程,如表 36.9 所示。這些支援例程的要求在第 65.1.3 節中有更詳細的解釋。
表 36.9. B-Tree 支援函式
函式 | 支援號 |
---|---|
比較兩個鍵並返回一個小於零、零或大於零的整數,表示第一個鍵是否小於、等於或大於第二個鍵 | 1 |
返回 C 可呼叫排序支援例程的地址(可選) | 2 |
比較測試值與基值加上/減去一個偏移量,並根據比較結果返回 true 或 false(可選) | 3 |
確定使用該運算子類的索引應用 btree 重複資料刪除最佳化是否安全(可選) | 4 |
定義特定於此運算子類的選項(可選) | 5 |
返回 C 可呼叫跳躍支援例程的地址(可選) | 6 |
Hash 索引需要一個支援例程,並允許根據運算子類作者的選擇提供另外兩個,如表 36.10 所示。
表 36.10. Hash 支援函式
函式 | 支援號 |
---|---|
計算鍵的 32 位雜湊值 | 1 |
計算給定 64 位鹽值的 64 位雜湊值;如果鹽值為 0,則結果的低 32 位必須與函式 1 將計算出的值匹配(可選) | 2 |
定義特定於此運算子類的選項(可選) | 3 |
GiST 索引有十二個支援例程,其中七個是可選的,如表 36.11 所示。(更多資訊請參閱第 65.2 節。)
表 36.11. GiST 支援函式
函式 | 描述 | 支援號 |
---|---|---|
一致性 |
確定鍵是否滿足查詢限定條件 | 1 |
並集 |
計算鍵集的並集 | 2 |
壓縮 |
計算要索引的鍵或值的壓縮表示(可選) | 3 |
解壓縮 |
計算壓縮鍵的解壓縮表示(可選) | 4 |
懲罰 |
計算將新鍵插入給定子樹的子樹的懲罰 | 5 |
選擇拆分 |
確定頁面中的哪些條目要移到新頁面,並計算結果頁面的聯合鍵 | 6 |
相同 |
比較兩個鍵,如果它們相等則返回 true | 7 |
距離 |
確定鍵到查詢值的距離(可選) | 8 |
提取 |
為僅索引掃描計算壓縮鍵的原始表示(可選) | 9 |
options |
定義特定於此運算子類的選項(可選) | 10 |
sortsupport |
提供用於快速索引構建的排序比較器(可選) | 11 |
translate_cmptype |
將比較型別轉換為運算子類使用的策略號(可選) | 12 |
SP-GiST 索引有六個支援例程,其中一個可選,如表 36.12 所示。(更多資訊請參閱第 65.3 節。)
表 36.12. SP-GiST 支援函式
函式 | 描述 | 支援號 |
---|---|---|
配置 |
提供運算子類的基本資訊 | 1 |
選擇 |
確定如何將新值插入內部元組 | 2 |
選擇拆分 |
確定如何劃分值集 | 3 |
inner_consistent |
確定需要搜尋哪些子分割槽以進行查詢 | 4 |
leaf_consistent |
確定鍵是否滿足查詢限定條件 | 5 |
options |
定義特定於此運算子類的選項(可選) | 6 |
GIN 索引有七個支援例程,其中四個是可選的,如表 36.13 所示。(更多資訊請參閱第 65.4 節。)
表 36.13. GIN 支援函式
函式 | 描述 | 支援號 |
---|---|---|
比較 |
比較兩個鍵並返回一個小於零、零或大於零的整數,表示第一個鍵是否小於、等於或大於第二個鍵 | 1 |
extractValue |
從要索引的值中提取鍵 | 2 |
extractQuery |
從查詢條件中提取鍵 | 3 |
一致性 |
確定值是否滿足查詢條件(布林變體)(如果存在支援函式 6,則可選) | 4 |
comparePartial |
比較查詢中的部分鍵和索引中的鍵,並返回一個小於零、零或大於零的整數,表示 GIN 是否應忽略此索引條目、將條目視為匹配或停止索引掃描(可選) | 5 |
triConsistent |
確定值是否滿足查詢條件(三元變體)(如果存在支援函式 4,則可選) | 6 |
options |
定義特定於此運算子類的選項(可選) | 7 |
BRIN 索引有五個基本支援例程,其中一個可選,如表 36.14 所示。一些基本例程的版本需要提供額外的支援例程。(更多資訊請參閱第 65.5.3 節。)
表 36.14. BRIN 支援函式
函式 | 描述 | 支援號 |
---|---|---|
opcInfo |
返回描述被索引列的摘要資料的內部資訊 | 1 |
add_value |
將新值新增到現有的摘要索引元組 | 2 |
一致性 |
確定值是否滿足查詢條件 | 3 |
並集 |
計算兩個摘要元組的並集 | 4 |
options |
定義特定於此運算子類的選項(可選) | 5 |
與搜尋運算子不同,支援例程返回特定索引方法期望的任何資料型別;例如,對於 B-tree 的比較函式,返回有符號整數。每個支援例程的引數數量和型別同樣取決於索引方法。對於 B-tree 和 hash,比較和雜湊支援例程接受與運算子類中包含的運算子相同型別的輸入資料,但對於大多數 GiST、SP-GiST、GIN 和 BRIN 支援例程則不是如此。
現在我們已經瞭解了基本概念,下面是建立新運算子類的承諾示例。(您可以在原始碼發行版的 src/tutorial/complex.c
和 src/tutorial/complex.sql
檔案中找到此示例的工作副本。)該運算子類封裝了按絕對值排序複數的運算子,因此我們選擇名稱 complex_abs_ops
。首先,我們需要一組運算子。定義運算子的過程在第 36.14 節中討論過。對於 B-tree 上的運算子類,我們需要以下運算子:
定義相關運算子集的最不容易出錯的方法是首先編寫 B-tree 比較支援例程,然後將其他函式寫成支援例程的單行包裝器。這可以減少在處理邊界情況時出現不一致結果的機率。按照這個方法,我們首先編寫
#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y) static int complex_abs_cmp_internal(Complex *a, Complex *b) { double amag = Mag(a), bmag = Mag(b); if (amag < bmag) return -1; if (amag > bmag) return 1; return 0; }
現在,小於函式如下所示:
PG_FUNCTION_INFO_V1(complex_abs_lt); Datum complex_abs_lt(PG_FUNCTION_ARGS) { Complex *a = (Complex *) PG_GETARG_POINTER(0); Complex *b = (Complex *) PG_GETARG_POINTER(1); PG_RETURN_BOOL(complex_abs_cmp_internal(a, b) < 0); }
其他四個函式僅在它們如何將內部函式的結果與零進行比較時有所不同。
接下來,我們將函式和基於函式的 SQL 宣告宣告為:
CREATE FUNCTION complex_abs_lt(complex, complex) RETURNS bool
AS 'filename
', 'complex_abs_lt'
LANGUAGE C IMMUTABLE STRICT;
CREATE OPERATOR < (
leftarg = complex, rightarg = complex, procedure = complex_abs_lt,
commutator = > , negator = >= ,
restrict = scalarltsel, join = scalarltjoinsel
);
指定正確的換位符和否定符運算子,以及合適的限制和連線選擇性函式非常重要,否則最佳化器將無法有效地利用索引。
這裡還有其他值得注意的地方。
對於型別為 complex
的兩個運算元,只能有一個名為 =
的運算子。在本例中,我們沒有其他 complex
型別的 =
運算子,但如果我們正在構建一個實際的資料型別,我們可能會希望 =
是複數的普通相等運算(而不是絕對值相等)。在這種情況下,我們需要為 complex_abs_eq
使用另一個運算子名稱。
雖然PostgreSQL可以處理具有相同 SQL 名稱的函式,只要它們的引數資料型別不同,C 語言就只能處理一個具有給定名稱的全域性函式。因此,我們不應將 C 函式命名為像 abs_eq
這樣簡單的名稱。通常,在 C 函式名稱中包含資料型別名稱是一個好習慣,以免與其他資料型別的函式衝突。
為了簡單起見,我們使 C 級別和 SQL 級別的函式名稱相同,但也可以將 SQL 函式名指定為 abs_eq
,並依賴PostgreSQL根據引數資料型別來區分它。
下一步是 B-tree 所需的支援例程的註冊。實現這一點的示例 C 程式碼與包含運算子函式的同一檔案中。這是我們宣告函式的方式:
CREATE FUNCTION complex_abs_cmp(complex, complex)
RETURNS integer
AS 'filename
'
LANGUAGE C IMMUTABLE STRICT;
現在我們有了所需的運算子和支援例程,我們終於可以建立運算子類了。
CREATE OPERATOR CLASS complex_abs_ops DEFAULT FOR TYPE complex USING btree AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 complex_abs_cmp(complex, complex);
大功告成!現在應該可以為 complex
列建立和使用 B-tree 索引了。
我們可以更詳細地編寫運算子條目,例如:
OPERATOR 1 < (complex, complex) ,
但當運算子接受我們正在為其定義運算子類的相同資料型別時,就沒有必要這樣做了。
上面的例子假定您希望將這個新的運算子類設定為 complex
資料型別的預設 B-tree 運算子類。如果您不希望這樣,只需省略 DEFAULT
關鍵字。
到目前為止,我們已經隱式地假定一個運算子類只處理一種資料型別。雖然在特定的索引列中只能有一種資料型別,但對一個數據型別的列與另一個數據型別的值之間的索引操作通常很有用。此外,如果一個運算子與某個運算子類相關聯,並且該運算子跨越不同資料型別,那麼另一個數據型別通常也有其自己的相關運算子類。顯式地建立相關類之間的聯絡是有益的,因為這可以幫助規劃器最佳化 SQL 查詢(特別是對於 B-tree 運算子類,因為規劃器包含大量關於如何處理它們的知識)。
為了滿足這些需求,PostgreSQL使用運算子族的概念。一個運算子族包含一個或多個運算子類,並且還可以包含可索引的運算子和屬於整個族但屬於族中任何單個類的相應支援函式。我們說這些運算子和函式在族中是“鬆散的”,而不是繫結到特定類。通常,每個運算子類包含單資料型別運算子,而跨資料型別的運算子在族中是鬆散的。
運算子族中的所有運算子和函式必須具有相容的語義,相容性要求由索引方法設定。因此,您可能會想為什麼還要將特定子集劃分出來作為運算子類;實際上,對於許多目的,類別的劃分並不重要,族是唯一有趣的分組。定義運算子類的原因是它們指定了支援任何特定索引所需族的一部分。如果存在使用某個運算子類的索引,那麼該運算子類不能在不刪除索引的情況下被刪除——但運算子族的其他部分,即其他運算子類和鬆散運算子,則可以被刪除。因此,應將運算子類指定為包含處理特定資料型別的索引所合理必需的最小運算子和函式集,然後可以將相關但不必要的其他運算子作為運算子族的鬆散成員新增。
例如,PostgreSQL有一個內建的 B-tree 運算子族 integer_ops
,它包含分別用於 bigint
(int8
)、integer
(int4
)和 smallint
(int2
)列的索引的運算子類 int8_ops
、int4_ops
和 int2_ops
。該族還包含跨資料型別的比較運算子,允許比較這些型別中的任意兩種,以便使用另一種型別的值來搜尋其中一種型別的索引。可以透過以下定義來複制該族:
CREATE OPERATOR FAMILY integer_ops USING btree; CREATE OPERATOR CLASS int8_ops DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS -- standard int8 comparisons OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint8cmp(int8, int8) , FUNCTION 2 btint8sortsupport(internal) , FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) , FUNCTION 4 btequalimage(oid) , FUNCTION 6 btint8skipsupport(internal) ; CREATE OPERATOR CLASS int4_ops DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS -- standard int4 comparisons OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint4cmp(int4, int4) , FUNCTION 2 btint4sortsupport(internal) , FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) , FUNCTION 4 btequalimage(oid) , FUNCTION 6 btint4skipsupport(internal) ; CREATE OPERATOR CLASS int2_ops DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS -- standard int2 comparisons OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint2cmp(int2, int2) , FUNCTION 2 btint2sortsupport(internal) , FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) , FUNCTION 4 btequalimage(oid) , FUNCTION 6 btint2skipsupport(internal) ; ALTER OPERATOR FAMILY integer_ops USING btree ADD -- cross-type comparisons int8 vs int2 OPERATOR 1 < (int8, int2) , OPERATOR 2 <= (int8, int2) , OPERATOR 3 = (int8, int2) , OPERATOR 4 >= (int8, int2) , OPERATOR 5 > (int8, int2) , FUNCTION 1 btint82cmp(int8, int2) , -- cross-type comparisons int8 vs int4 OPERATOR 1 < (int8, int4) , OPERATOR 2 <= (int8, int4) , OPERATOR 3 = (int8, int4) , OPERATOR 4 >= (int8, int4) , OPERATOR 5 > (int8, int4) , FUNCTION 1 btint84cmp(int8, int4) , -- cross-type comparisons int4 vs int2 OPERATOR 1 < (int4, int2) , OPERATOR 2 <= (int4, int2) , OPERATOR 3 = (int4, int2) , OPERATOR 4 >= (int4, int2) , OPERATOR 5 > (int4, int2) , FUNCTION 1 btint42cmp(int4, int2) , -- cross-type comparisons int4 vs int8 OPERATOR 1 < (int4, int8) , OPERATOR 2 <= (int4, int8) , OPERATOR 3 = (int4, int8) , OPERATOR 4 >= (int4, int8) , OPERATOR 5 > (int4, int8) , FUNCTION 1 btint48cmp(int4, int8) , -- cross-type comparisons int2 vs int8 OPERATOR 1 < (int2, int8) , OPERATOR 2 <= (int2, int8) , OPERATOR 3 = (int2, int8) , OPERATOR 4 >= (int2, int8) , OPERATOR 5 > (int2, int8) , FUNCTION 1 btint28cmp(int2, int8) , -- cross-type comparisons int2 vs int4 OPERATOR 1 < (int2, int4) , OPERATOR 2 <= (int2, int4) , OPERATOR 3 = (int2, int4) , OPERATOR 4 >= (int2, int4) , OPERATOR 5 > (int2, int4) , FUNCTION 1 btint24cmp(int2, int4) , -- cross-type in_range functions FUNCTION 3 in_range(int4, int4, int8, boolean, boolean) , FUNCTION 3 in_range(int4, int4, int2, boolean, boolean) , FUNCTION 3 in_range(int2, int2, int8, boolean, boolean) , FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;
請注意,此定義“過載”了運算子策略和支援函式編號:每個編號在族中出現多次。這是允許的,只要特定編號的每個例項都具有不同的輸入資料型別。輸入型別均等於運算子類的輸入型別的例項是該運算子類的主要運算子和支援函式,在大多數情況下應將其宣告為運算子類的一部分,而不是作為族中的鬆散成員。
在 B-tree 運算子族中,族中的所有運算子必須排序相容,詳細說明請參閱第 65.1.2 節。對於族中的每個運算子,必須有一個支援函式,其輸入資料型別與運算子相同。建議一個族是完整的,即對於所有資料型別組合都包含所有運算子。每個運算子類應僅包含其資料型別的非跨型別運算子和支援函式。
要構建一個多資料型別的 hash 運算子族,必須為該族支援的每種資料型別建立相容的 hash 支援函式。相容性意味著對於由該族等價運算子視為相等的任何兩個值,即使這些值型別不同,這些函式也保證返回相同的雜湊碼。當型別具有不同的物理表示時,這通常很難實現,但在某些情況下是可以做到的。此外,透過隱式或二進位制強制轉換將一種資料型別的值轉換為運算子族中表示的另一種資料型別,不應改變計算出的雜湊值。請注意,每個資料型別只有一個支援函式,而不是每個等價運算子一個。建議一個族是完整的,即為每種資料型別組合提供一個等價運算子。每個運算子類應僅包含其資料型別的非跨型別等價運算子和支援函式。
GiST、SP-GiST 和 GIN 索引沒有任何關於跨資料型別操作的顯式概念。支援的運算子集就是給定運算子類的主支援函式能夠處理的任何運算子。
在 BRIN 中,要求取決於提供運算子類的框架。對於基於minmax
的運算子類,所要求的行為與 B-tree 運算子族相同:族中的所有運算子必須排序相容,並且強制轉換不應改變相關的排序。
PostgreSQL 8.3 之前的版本沒有運算子族的概念,因此任何打算與索引一起使用的跨資料型別運算子都必須直接繫結到索引的運算子類中。雖然這種方法仍然有效,但已棄用,因為它使得索引的依賴性過於廣泛,並且當兩種資料型別都具有相同的運算子族中的運算子時,規劃器可以更有效地處理跨資料型別比較。
PostgreSQL使用運算子類來推斷運算子的屬性,而不僅僅是它們是否可用於索引。因此,即使您無意為使用者定義的資料型別建立索引,也可能需要建立運算子類。
特別是,存在一些 SQL 功能,如 ORDER BY
和 DISTINCT
,它們需要比較和排序值。要在使用者定義的資料型別上實現這些功能,PostgreSQL會查詢該資料型別的預設 B-tree 運算子類。“等於”成員定義了系統對 GROUP BY
和 DISTINCT
的值相等的概念,而運算子類施加的排序定義了預設的 ORDER BY
排序。
如果資料型別沒有預設的 B-tree 運算子類,系統將查詢預設的 hash 運算子類。但由於這種運算子類只提供相等性,它只能支援分組而不是排序。
當資料型別沒有預設運算子類時,如果您嘗試使用這些 SQL 功能,您將收到類似“無法識別排序運算子”的錯誤。
在PostgreSQL 7.4 之前的版本中,排序和分組操作會隱式地使用名為 =
、<
和 >
的運算子。現在依賴預設運算子類的新行為,避免了對具有特定名稱的運算子行為進行任何假設。
可以透過在 USING
選項中指定類的小於運算子來按非預設 B-tree 運算子類進行排序,例如:
SELECT * FROM mytable ORDER BY somecol USING ~<~;
或者,在 USING
中指定類的大於運算子可以選擇降序排序。
使用者定義型別的陣列的比較也依賴於型別預設 B-tree 運算子類定義的語義。如果不存在預設的 B-tree 運算子類,但存在預設的 hash 運算子類,則支援陣列相等性,但不支援順序比較。
另一個需要更多資料型別特定知識的 SQL 功能是視窗函式的 RANGE
offset
PRECEDING
/FOLLOWING
框架選項(參見第 4.2.8 節)。對於像這樣的查詢:
SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING) FROM mytable;
僅僅知道如何按 x
排序是不夠的;資料庫還必須理解如何將當前行的 x
值“減去 5”或“加上 10”,以確定當前視窗框架的邊界。使用定義 ORDER BY
排序的 B-tree 運算子類提供的比較運算子可以與行x
值進行比較——但是加法和減法運算子不是運算子類的一部分,那麼應該使用哪一個呢?硬編碼該選擇是不理想的,因為不同的排序順序(不同的 B-tree 運算子類)可能需要不同的行為。因此,B-tree 運算子類可以指定一個in_range 支援函式,該函式封裝了對其排序順序有意義的加法和減法行為。它甚至可以提供多個 in_range 支援函式,以防有多種資料型別適合用作 RANGE
子句中的偏移量。如果與視窗的 ORDER BY
子句關聯的 B-tree 運算子類沒有匹配的 in_range 支援函式,則不支援 RANGE
offset
PRECEDING
/FOLLOWING
選項。
另一個要點是,出現在 hash 運算子族中的等價運算子是 hash 連線、hash 聚合和相關最佳化的候選。hash 運算子族在這裡至關重要,因為它標識了要使用的 hash 函式。
某些索引訪問方法(目前只有 GiST 和 SP-GiST)支援排序運算子的概念。我們到目前為止所討論的都是搜尋運算子。搜尋運算子是指索引可以用於查詢滿足 WHERE
indexed_column
operator
constant
的所有行的運算子。請注意,這並沒有保證返回匹配行的順序。相比之下,排序運算子不限制可返回的行集,而是確定它們的順序。排序運算子是指索引可以按 ORDER BY
indexed_column
operator
constant
所表示的順序掃描以返回行的運算子。之所以這樣定義排序運算子,是因為它支援最近鄰搜尋,如果運算子是測量距離的運算子。例如,一個像這樣的查詢:
SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
查詢離給定目標點最近的十個地點。位置列上的 GiST 索引可以有效地做到這一點,因為 <->
是一個排序運算子。
雖然搜尋運算子必須返回布林結果,但排序運算子通常返回其他型別,例如距離的浮點數或數值。此型別通常與被索引的資料型別不同。為了避免硬編碼對不同資料型別行為的假設,排序運算子的定義要求命名一個 B-tree 運算子族,該族指定結果資料型別的排序順序。如前一節所述,B-tree 運算子族定義了PostgreSQL的排序概念,因此這是一個自然的表示。由於點 <->
運算子返回 float8
,因此可以在建立運算子類的命令中這樣指定:
OPERATOR 15 <-> (point, point) FOR ORDER BY float_ops
其中 float_ops
是包含 float8
操作的內建運算子族。此宣告表明索引能夠按 <->
運算子的遞增值順序返回行。
運算子類還有兩個我們尚未討論的特殊功能,主要是因為它們不適用於最常用的索引方法。
通常,將一個運算子宣告為運算子類(或族)的成員意味著索引方法可以檢索滿足 WHERE
條件的行集,並使用該運算子。例如:
SELECT * FROM table WHERE integer_column < 4;
可以透過整數列上的 B-tree 索引精確滿足。但有時索引可以作為匹配行的不精確指南。例如,如果 GiST 索引只儲存幾何物件的邊界框,那麼它無法精確滿足測試非矩形物件(如多邊形)之間重疊的 WHERE
條件。然而,我們可以使用該索引找到其邊界框與目標物件的邊界框重疊的物件,然後在僅由索引找到的物件上執行精確的重疊測試。如果出現這種情況,則稱該索引對於該運算子是“有損的”。有損索引搜尋是透過讓索引方法在一行可能或可能不真正滿足查詢條件時返回一個recheck標誌來實現的。然後,核心系統將對檢索到的行測試原始查詢條件,以檢視是否應將其作為有效匹配返回。當索引保證返回所有必需的行,再加上一些可能透過執行原始運算子呼叫而被消除的附加行時,這種方法就會奏效。支援有損搜尋的索引方法(目前是 GiST、SP-GiST 和 GIN)允許各個運算子類的支援函式設定 recheck 標誌,因此這本質上是運算子類的一個功能。
再次考慮儲存在索引中的多邊形等複雜物件的邊界框的情況。在這種情況下,將整個多邊形儲存在索引條目中沒有多大價值——我們可以儲存一個更簡單的 box
型別物件。這種情況透過 CREATE OPERATOR CLASS
中的 STORAGE
選項來表示:我們可以這樣寫:
CREATE OPERATOR CLASS polygon_ops DEFAULT FOR TYPE polygon USING gist AS ... STORAGE box;
目前,只有 GiST、SP-GiST、GIN 和 BRIN 索引方法支援與列資料型別不同的 STORAGE
型別。當使用 STORAGE
時,GiST 的 compress
和 decompress
支援例程必須處理資料型別轉換。SP-GiST 同樣需要一個 compress
支援例程將資料型別轉換為儲存型別(如果不同);如果 SP-GiST opclass 還支援檢索資料,則反向轉換必須由 consistent
函式處理。在 GIN 中,STORAGE
型別標識“鍵”值的型別,這通常與被索引列的型別不同——例如,整數陣列列的運算子類可能具有隻是整數的鍵。GIN 的 extractValue
和 extractQuery
支援例程負責從索引值中提取鍵。BRIN 與 GIN 類似:STORAGE
型別標識儲存的摘要值的型別,並且運算子類的支援過程負責正確解釋摘要值。
如果您在文件中發現任何不正確的內容、與您使用的特定功能的體驗不符的內容,或需要進一步說明的內容,請使用此表單報告文件問題。