到目前为止描述的过程允许我们定义新的类型、新的函数和新的操作符。但是,我们还不能为一个新数据类型的列定义索引。要做到这一点,我们必须为新数据类型定义一个操作符类。在本节后面,我们将通过一个例子来说明这个概念:为 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
类型标识存储的摘要值的类型,并且操作符类的支持过程负责正确解释摘要值。
如果您在文档中发现任何不正确的内容、与您使用的特定功能的体验不符的内容,或需要进一步说明的内容,请使用此表单报告文档问题。