到目前为止描述的过程允许您定义新类型、新函数和新操作符。但是,我们还不能对新数据类型的一列定义索引。要做到这一点,我们必须为新数据类型定义一个操作符类。在本节后面,我们将通过一个示例来说明这个概念:一个新的 B 树索引方法操作符类,它以升序绝对值顺序存储和排序复数。
操作符类可以分组到操作符族中,以显示语义兼容类之间的关系。当只涉及一个数据类型时,一个操作符类就足够了,所以我们先关注这种情况,然后返回到操作符族。
pg_am
表为每个索引方法(内部称为访问方法)包含一行。对表的常规访问支持内置于 PostgreSQL 中,但所有索引方法都在 pg_am
中描述。可以通过编写必要的代码,然后在 pg_am
中创建条目来添加新的索引访问方法——但这超出了本章的范围(请参阅 第 64 章)。
索引方法的例程不会直接了解索引方法将操作的数据类型。相反,操作符类 标识索引方法需要使用的一组操作,以便使用特定数据类型。之所以将它们称为操作符类,是因为它们指定的内容之一是可以与索引一起使用的 WHERE
子句操作符(即,可以转换为索引扫描限定符)。操作符类还可以指定索引方法的内部操作所需的某些支持函数,但不会直接对应于可以与索引一起使用的任何 WHERE
子句操作符。
可以为相同的数据类型和索引方法定义多个操作符类。通过执行此操作,可以为单个数据类型定义多组索引语义。例如,B 树索引需要为其处理的每种数据类型定义排序顺序。对于复数数据类型来说,可能需要一个按复数绝对值对数据进行排序的 B 树操作符类,另一个按实部进行排序,依此类推。通常,一个操作符类会被认为是最常用的,并将被标记为该数据类型和索引方法的默认操作符类。
相同的操作符类名称可用于多个不同的索引方法(例如,B 树和哈希索引方法都具有名为 int4_ops
的操作符类),但每个此类类都是一个独立的实体,必须单独定义。
与操作符类关联的操作符由“策略编号”标识,策略编号用于标识每个操作符在其操作符类上下文中的语义。例如,B 树对键施加严格的顺序,从较小到较大,因此“小于”和“大于或等于”等操作符对于 B 树来说很有趣。由于 PostgreSQL 允许用户定义操作符,因此 PostgreSQL 无法查看操作符的名称(例如,<
或 >=
)并判断它是什么类型的比较。相反,索引方法定义了一组“策略”,可以将其视为广义操作符。每个操作符类指定哪个实际操作符对应于特定数据类型和索引语义解释的每个策略。
B 树索引方法定义了五种策略,如 表 38.3 所示。
表 38.3. B 树策略
操作 | 策略编号 |
---|---|
小于 | 1 |
小于或等于 | 2 |
等于 | 3 |
大于或等于 | 4 |
大于 | 5 |
哈希索引仅支持相等比较,因此只使用一种策略,如 表 38.4 所示。
表 38.4. 哈希策略
操作 | 策略编号 |
---|---|
等于 | 1 |
GiST 索引更灵活:它们根本没有固定的策略集。相反,每个特定 GiST 运算符类的 “一致性” 支持例程会根据需要解释策略编号。例如,几个内置 GiST 索引运算符类对二维几何对象进行索引,提供 表 38.5 中所示的 “R 树” 策略。其中四个是真正的二维测试(重叠、相同、包含、被包含);四个只考虑 X 方向;其他四个在 Y 方向提供相同的测试。
表 38.5. GiST 二维 “R 树” 策略
操作 | 策略编号 |
---|---|
严格位于左侧 | 1 |
不延伸到右侧 | 2 |
重叠 | 3 |
不延伸到左侧 | 4 |
严格位于右侧 | 5 |
相同 | 6 |
包含 | 7 |
被包含 | 8 |
不延伸到上方 | 9 |
严格位于下方 | 10 |
严格位于上方 | 11 |
不延伸到下方 | 12 |
SP-GiST 索引在灵活性方面类似于 GiST 索引:它们没有固定的策略集。相反,每个运算符类的支持例程会根据运算符类的定义解释策略编号。例如,内置运算符类用于点策略编号如 表 38.6 所示。
表 38.6. SP-GiST 点策略
操作 | 策略编号 |
---|---|
严格位于左侧 | 1 |
严格位于右侧 | 5 |
相同 | 6 |
被包含 | 8 |
严格位于下方 | 10 |
严格位于上方 | 11 |
GIN 索引类似于 GiST 和 SP-GiST 索引,因为它们也没有固定的策略集。相反,每个运算符类的支持例程会根据运算符类的定义解释策略编号。例如,内置运算符类用于数组策略编号如 表 38.7 所示。
表 38.7. GIN 数组策略
操作 | 策略编号 |
---|---|
重叠 | 1 |
包含 | 2 |
被包含 | 3 |
等于 | 4 |
BRIN 索引与 GiST、SP-GiST 和 GIN 索引类似,它们也没有固定的策略集。相反,每个运算符类的支持例程根据运算符类的定义解释策略号。例如,内置 Minmax
运算符类使用的策略号显示在 表 38.8 中。
表 38.8. BRIN Minmax 策略
操作 | 策略编号 |
---|---|
小于 | 1 |
小于或等于 | 2 |
等于 | 3 |
大于或等于 | 4 |
大于 | 5 |
请注意,上面列出的所有运算符都返回布尔值。实际上,所有定义为索引方法搜索运算符的运算符都必须返回 boolean
类型,因为它们必须出现在 WHERE
子句的顶层才能与索引一起使用。(一些索引访问方法还支持 排序运算符,它们通常不返回布尔值;此功能在 第 38.16.7 节 中讨论。)
策略通常不足以让系统弄清楚如何使用索引。实际上,索引方法需要额外的支持例程才能工作。例如,B 树索引方法必须能够比较两个键并确定一个大于、等于或小于另一个。类似地,哈希索引方法必须能够为键值计算哈希码。这些操作不对应于 SQL 命令中限定符中使用的运算符;它们是索引方法在内部使用的管理例程。
与策略一样,运算符类标识哪些特定函数应针对给定的数据类型和语义解释扮演这些角色。索引方法定义它需要的函数集,而运算符类通过将它们分配给索引方法指定的 “支持函数号” 来标识要使用的正确函数。
此外,一些 opclass 允许用户指定控制其行为的参数。每个内置索引访问方法都有一个可选的 options
支持函数,它定义了一组特定于 opclass 的参数。
B 树需要一个比较支持函数,并允许在运算符类作者的选项中提供四个附加支持函数,如 表 38.9 所示。这些支持函数的要求在 第 67.3 节 中进一步解释。
表 38.9. B 树支持函数
函数 | 支持编号 |
---|---|
比较两个键,并返回小于零、零或大于零的整数,指示第一个键是否小于、等于或大于第二个键 | 1 |
返回 C 可调用排序支持函数的地址(可选) | 2 |
将测试值与基本值加/减偏移量进行比较,并根据比较结果返回真或假(可选) | 3 |
确定是否可以安全地对使用运算符类的索引应用 btree 去重优化(可选) | 4 |
定义特定于此运算符类的选项(可选) | 5 |
哈希索引需要一个支持函数,并允许在运算符类作者的选项中提供两个附加函数,如 表 38.10 所示。
表 38.10. 哈希支持函数
函数 | 支持编号 |
---|---|
计算键的 32 位哈希值 | 1 |
给定 64 位盐值计算键的 64 位哈希值;如果盐值为 0,则结果的低 32 位必须与函数 1 计算的值匹配(可选) | 2 |
定义特定于此运算符类的选项(可选) | 3 |
GiST 索引有 11 个支持函数,其中 6 个是可选的,如 表 38.11 所示。(有关更多信息,请参阅 第 68 章。)
表 38.11. GiST 支持函数
函数 | 说明 | 支持编号 |
---|---|---|
一致 |
确定键是否满足查询限定符 | 1 |
并集 |
计算一组键的并集 | 2 |
压缩 |
计算要编入索引的键或值的压缩表示形式(可选) | 3 |
解压缩 |
计算压缩键的解压缩表示形式(可选) | 4 |
惩罚 |
计算将新键插入到具有给定子树键的子树中的惩罚 | 5 |
选择拆分 |
确定要移动到新页面的页面的条目,并计算结果页面的并集键 | 6 |
相同 |
比较两个键,如果它们相等,则返回真 | 7 |
距离 |
确定从键到查询值的距离(可选) | 8 |
获取 |
计算压缩键的原始表示形式以进行仅索引扫描(可选) | 9 |
选项 |
定义特定于此运算符类的选项(可选) | 10 |
排序支持 |
提供用于快速索引构建的排序比较器(可选) | 11 |
SP-GiST 索引有 6 个支持函数,其中 1 个是可选的,如 表 38.12 所示。(有关更多信息,请参阅 第 69 章。)
表 38.12. SP-GiST 支持函数
函数 | 说明 | 支持编号 |
---|---|---|
config |
提供有关运算符类的基本信息 | 1 |
choose |
确定如何将新值插入内部元组 | 2 |
选择拆分 |
确定如何对一组值进行分区 | 3 |
inner_consistent |
确定查询需要搜索哪些子分区 | 4 |
leaf_consistent |
确定键是否满足查询限定符 | 5 |
选项 |
定义特定于此运算符类的选项(可选) | 6 |
如 表 38.13 所示,GIN 索引有七个支持函数,其中四个是可选的。(有关详细信息,请参阅 第 70 章。)
表 38.13. GIN 支持函数
函数 | 说明 | 支持编号 |
---|---|---|
compare |
比较两个键并返回小于零、零或大于零的整数,表示第一个键是否小于、等于或大于第二个键 | 1 |
extractValue |
从要编入索引的值中提取键 | 2 |
extractQuery |
从查询条件中提取键 | 3 |
一致 |
确定值是否与查询条件匹配(布尔变量)(如果支持函数 6 存在,则可选) | 4 |
comparePartial |
比较查询中的部分键和索引中的键,并返回小于零、零或大于零的整数,表示 GIN 应忽略此索引条目、将条目视为匹配或停止索引扫描(可选) | 5 |
triConsistent |
确定值是否与查询条件匹配(三元变量)(如果支持函数 4 存在,则可选) | 6 |
选项 |
定义特定于此运算符类的选项(可选) | 7 |
如 表 38.14 所示,BRIN 索引有五个基本支持函数,其中一个可选。某些版本的基本函数需要提供其他支持函数。(有关详细信息,请参阅 第 71.3 节。)
表 38.14. BRIN 支持函数
函数 | 说明 | 支持编号 |
---|---|---|
opcInfo |
返回描述已编入索引的列的摘要数据的内部信息 | 1 |
add_value |
向现有摘要索引元组添加新值 | 2 |
一致 |
确定值是否与查询条件匹配 | 3 |
并集 |
计算两个摘要元组的并集 | 4 |
选项 |
定义特定于此运算符类的选项(可选) | 5 |
与搜索运算符不同,支持函数返回特定索引方法期望的任何数据类型;例如,对于 B 树的比较函数,返回有符号整数。每个支持函数的参数数量和类型同样取决于索引方法。对于 B 树和哈希,比较和哈希支持函数采用与运算符类中包含的运算符相同的数据类型作为输入,但大多数 GiST、SP-GiST、GIN 和 BRIN 支持函数并非如此。
现在我们已经了解了这些概念,下面是创建新运算符类的示例。(您可以在源发行版中的 src/tutorial/complex.c
和 src/tutorial/complex.sql
中找到此示例的工作副本。)运算符类封装了按绝对值顺序对复数进行排序的运算符,因此我们选择名称 complex_abs_ops
。首先,我们需要一组运算符。在 第 38.14 节 中讨论了定义运算符的过程。对于 B 树上的运算符类,我们需要的运算符是
定义一组相关的比较运算符最不易出错的方法是首先编写 B 树比较支持函数,然后将其他函数编写为围绕支持函数的一行包装器。这减少了在特殊情况下获得不一致结果的可能性。按照此方法,我们首先编写
#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 函数名称中包含数据类型名称,以免与其他数据类型的函数冲突。
我们可以将函数的 SQL 名称设为 abs_eq
,依靠 PostgreSQL 通过参数数据类型将其与任何其他同名 SQL 函数区分开来。为了使示例简单,我们使函数在 C 级和 SQL 级具有相同的名称。
下一步是注册 B 树所需的支撑例程。实现此功能的示例 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 树索引了。
我们可以更详细地编写运算符条目,如下所示
OPERATOR 1 < (complex, complex) ,
但是,当运算符采用与我们定义运算符类的相同数据类型时,无需这样做。
上述示例假设您希望将此新运算符类设为 complex
数据类型的默认 B 树运算符类。如果不希望这样做,只需省略单词 DEFAULT
。
到目前为止,我们隐含地假设一个运算符类只处理一种数据类型。虽然一个特定索引列中肯定只能有一种数据类型,但对将索引列与不同数据类型的值进行比较的操作进行索引通常很有用。此外,如果在与运算符类相关联时需要使用跨数据类型的运算符,则其他数据类型通常有其自己的相关运算符类。明确相关类之间的连接非常有用,因为这有助于规划器优化 SQL 查询(特别是对于 B 树运算符类,因为规划器包含大量有关如何使用它们的信息)。
为了满足这些需求,PostgreSQL 使用了运算符族的概念。运算符族包含一个或多个运算符类,还可以包含可索引的运算符和属于整个族而不是族内任何单个类的相应支撑函数。我们称此类运算符和函数在族内是“松散的”,而不是绑定到特定类中。通常,每个运算符类都包含单数据类型运算符,而跨数据类型运算符在族内是松散的。
运算符族中的所有运算符和函数必须具有兼容的语义,其中兼容性要求由索引方法设定。因此,您可能会疑惑为什么还要将族的特定子集挑出来作为运算符类;事实上,对于许多目的,类划分是无关紧要的,族是唯一有趣的组。定义运算符类的目的是指定需要多少族来支持任何特定索引。如果有一个使用运算符类的索引,那么不能在不删除索引的情况下删除该运算符类——但是运算符族的其他部分,即其他运算符类和松散运算符,可以删除。因此,应该指定一个运算符类来包含使用特定数据类型上的索引合理需要的最小运算符和函数集,然后可以将相关但非必要的运算符作为运算符族的松散成员添加进来。
例如,PostgreSQL 有一个内置的 B 树运算符族 integer_ops
,其中包括运算符类 int8_ops
、int4_ops
和 int2_ops
,分别用于 bigint
(int8
)、integer
(int4
) 和 smallint
(int2
) 列上的索引。该族还包含跨数据类型的比较运算符,允许对这两种类型中的任何两种类型进行比较,以便可以使用另一种类型的比较值来搜索其中一种类型的索引。该族可以通过以下定义进行复制
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) ; 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) ; 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) ; 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 树运算符族中,族中的所有运算符都必须按兼容方式进行排序,如 第 67.2 节 中详细指定的。对于族中的每个运算符,必须有一个支持函数,其具有与运算符相同的两个输入数据类型。建议族是完整的,即,对于数据类型的每个组合,都包括所有运算符。每个运算符类应仅包括其数据类型的非交叉类型运算符和支持函数。
要构建多数据类型哈希运算符族,必须为族支持的每种数据类型创建兼容的哈希支持函数。此处兼容性意味着,对于族相等性运算符认为相等的任何两个值,函数保证返回相同的哈希代码,即使这些值属于不同的类型。当类型具有不同的物理表示时,通常难以实现这一点,但在某些情况下可以实现。此外,通过隐式或二进制强制转换将运算符族中表示的一种数据类型的值转换为运算符族中表示的另一种数据类型,不得更改计算出的哈希值。请注意,每个数据类型只有一个支持函数,而不是每个相等性运算符一个。建议族是完整的,即,为数据类型的每个组合提供一个相等性运算符。每个运算符类应仅包括其数据类型的非交叉类型相等性运算符和支持函数。
GiST、SP-GiST 和 GIN 索引没有任何明确的交叉数据类型操作概念。支持的运算符集只是给定运算符类的主要支持函数可以处理的任何内容。
在 BRIN 中,要求取决于提供运算符类的框架。对于基于 minmax
的运算符类,所需的行为与 B 树运算符族相同:族中的所有运算符都必须按兼容方式进行排序,并且强制转换不得更改关联的排序顺序。
在 PostgreSQL 8.3 之前,没有运算符族的概念,因此任何打算与索引一起使用的跨数据类型的运算符都必须直接绑定到索引的运算符类中。虽然此方法仍然有效,但它已被弃用,因为它使索引的依赖性变得太广泛,并且当两种数据类型在同一运算符族中具有运算符时,规划器可以更有效地处理跨数据类型的比较。
PostgreSQL 使用运算符类来推断运算符的属性,而不仅仅是它们是否可以与索引一起使用。因此,即使您无意为任何数据类型列建立索引,您可能也想创建运算符类。
特别是,有诸如 ORDER BY
和 DISTINCT
等 SQL 特性,它们需要对值进行比较和排序。为了在用户定义的数据类型上实现这些特性,PostgreSQL 会查找数据类型的默认 B 树运算符类。此运算符类的 “equals” 成员定义了系统对 GROUP BY
和 DISTINCT
值相等性的概念,而运算符类施加的排序顺序定义了默认 ORDER BY
排序顺序。
如果数据类型没有默认 B 树运算符类,系统将查找默认哈希运算符类。但由于该类运算符仅提供相等性,因此它只能支持分组,而不能支持排序。
当数据类型没有默认运算符类时,如果您尝试对数据类型使用这些 SQL 特性,您将收到诸如 “无法识别排序运算符” 的错误。
在 7.4 之前的 PostgreSQL 版本中,排序和分组操作会隐式使用名为 =
、<
和 >
的运算符。依赖于默认运算符类的新行为避免了对具有特定名称的运算符的行为做出任何假设。
可以通过在 USING
选项中指定类的小于运算符来按非默认 B 树运算符类排序,例如
SELECT * FROM mytable ORDER BY somecol USING ~<~;
或者,在 USING
中指定类的大于运算符来选择降序排序。
用户定义类型的数组比较也依赖于由类型的默认 B 树运算符类定义的语义。如果没有默认 B 树运算符类,但有默认哈希运算符类,则支持数组相等,但不支持排序比较。
另一个需要更多特定于数据类型的知识的 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 树运算符类提供的比较运算符,可以将结果边界与其他行的 x
值进行比较,但加法和减法运算符不属于运算符类,因此应该使用哪些运算符?硬编码该选择是不合适的,因为不同的排序顺序(不同的 B 树运算符类)可能需要不同的行为。因此,B 树运算符类可以指定一个 in_range 支持函数,该函数封装了与其排序顺序相符的加法和减法行为。它甚至可以提供多个 in_range 支持函数,以防有多个数据类型可以用作 RANGE
子句中的偏移量。如果与窗口的 ORDER BY
子句关联的 B 树运算符类没有匹配的 in_range 支持函数,则不支持 RANGE
offset
PRECEDING
/FOLLOWING
选项。
另一个重要的一点是,哈希运算符族中出现的相等运算符是哈希联接、哈希聚合和相关优化的候选者。哈希运算符族在这里至关重要,因为它标识要使用的哈希函数。
某些索引访问方法(目前仅 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 树运算符系列,该系列指定结果数据类型的排序顺序。正如上一节中所述,B 树运算符系列定义了PostgreSQL的排序概念,因此这是一个自然的表示。由于点<->
运算符返回float8
,因此可以在类似这样的运算符类创建命令中指定它
OPERATOR 15 <-> (point, point) FOR ORDER BY float_ops
其中float_ops
是包含对float8
进行操作的内置运算符系列。此声明指出,索引能够按<->
运算符的增大值对行进行排序。
我们尚未讨论运算符类的两个特殊功能,主要是因为它们对于最常用的索引方法没有用。
通常,将运算符声明为运算符类(或族)的成员意味着索引方法可以使用运算符检索完全满足 WHERE
条件的行集。例如
SELECT * FROM table WHERE integer_column < 4;
可以用整数列上的 B 树索引完全满足。但在某些情况下,索引可用作匹配行的非精确指南。例如,如果 GiST 索引仅存储几何对象的边界框,则它无法完全满足测试非矩形对象(如多边形)之间重叠的 WHERE
条件。然而,我们可以使用索引查找其边界框与目标对象边界框重叠的对象,然后仅对索引找到的对象执行精确重叠测试。如果此场景适用,则索引对于运算符来说是 “有损” 的。有损索引搜索通过让索引方法在行可能或可能不真正满足查询条件时返回 重新检查 标志来实现。然后,核心系统将在检索的行上测试原始查询条件,以查看是否应将其作为有效匹配项返回。如果索引保证返回所有必需的行,以及可能的一些附加行(可以通过执行原始运算符调用来消除这些行),则此方法有效。支持有损搜索的索引方法(当前为 GiST、SP-GiST 和 GIN)允许单个运算符类的支持函数设置重新检查标志,因此这本质上是一个运算符类功能。
再次考虑我们仅在索引中存储复杂对象(如多边形)的边界框的情况。在这种情况下,在索引项中存储整个多边形没有多大价值——我们不妨只存储类型为 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
类型标识存储的摘要值的类型,运算符类的支持过程负责正确解释摘要值。