Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

36.16. 扩展与索引对接 #

36.16.1. 索引方法和运算符类型
36.16.2. 索引操作策略
36.16.3. 索引操作支持例程
36.16.4. 一个例子
36.16.5. 运算符类型和运算符簇
36.16.6. 系统如何依赖于运算符类型
36.16.7. 排序运算符
36.16.8. 运算符类型的特性

以上讲述的过程允许您定义新的类型、新的函数和新的运算符。但是,我们还不能在新的数据类型的列上定义一个索引。为此,我们必须为新的数据类型定义一个运算符类型。本节稍后,我们将通过一个例子来阐述这一概念:为 B 树类型索引方法定义一个新的运算符类型,用于以绝对值升序排列的方式存储和排序复数。

运算符类可以分组到运算符族中以展示语义兼容类的关系。当只涉及单个类型时,单个运算符类就已足够,因此我们先专注于此类情况,然后再返回到运算符族。

36.16.1. 索引方法和运算符类 #

运算符类与索引访问方法相关,例如 B 树GIN。可以使用 CREATE ACCESS METHOD 定义定制索引访问方法。请参阅 第 62 章 了解详情。

索引方法的例程不直接了解该索引方法将处理的数据类型。相反,运算符类 确定了该索引方法用来处理特定数据类型所需的操作集。运算符类之所以如此命名,是因为他们指定的一样东西是可用于索引的 WHERE 子句运算符集(即,可转换为索引扫描限定符)。运算符类还可指定索引方法的内部操作所需的某些支持函数,但不直接对应于可用于索引的任何 WHERE 子句运算符。

为相同的数据类型和索引方法定义多个运算符类是可能的。通过这样做,可以为单个数据类型定义多套索引语义。例如,B 树索引需要为其处理的每种数据类型定义排序顺序。对于复杂数字数据类型,将一个 B 树运算符类设定为按照复杂绝对值对数据进行排序会很有用,另一个设定为按照实部进行排序,依此类推。通常,其中一个运算符类会被认为最常用,并标记为此数据类型和索引方法的默认运算符类。

同一运算符类名称可用于多个不同的索引方法(例如,B 树和哈希索引方法都有名为 int4_ops 的运算符类),但每个此类都是一个独立实体,必须分别进行定义。

36.16.2. 索引方法策略 #

与运算符类相关联的运算符由“策略编号”标识,该编号用于识别每个运算符在其运算符类上下文中的语义。例如,B 树对键施加严格的顺序,即从较小到较大,因此诸如“小于”和“大于或等于”之类的运算符与 B 树有关。因为PostgreSQL允许用户定义运算符,PostgreSQL不能只考虑运算符的名称(例如,<>=)来识别该运算符是什么类型的比较。相反,索引方法定义了一组“策略”,可将其视为广义运算符。每个运算符类指定对于特定数据类型和索引语义的解释,实际的运算符与每个策略相对应。

B 树索引方法定义了五种策略,如表 36.3所示。

表 36.3。B 树策略

操作 策略编号
小于 1
小于或等于 2
等于 3
大于或等于 4
大于 5

哈希索引只支持相等比较,因此它们仅使用一种策略,如表 36.4所示。

表 36.4。哈希策略

操作 策略编号
等于 1

GiST 索引更加灵活:它们根本没有固定的策略集。相反,每个特定 GiST 运算符类的“一致性”支持例程会根据自己的需要来解释策略编号。例如,几个内置 GiST 索引运算符类对二维几何对象建立索引,提供了表 36.5所示的“R 树”策略。其中有四个是真正的二维测试(重叠、相同、包含、被包含);其中有四个只考虑 X 方向;其他四个在 Y 方向上提供相同的测试。

表 36.5。GiST 二维“R 树”策略

操作 策略编号
严格位于左侧 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 中讨论。)

36.16.3。索引方法支持例程 #

策略通常不足以让系统了解如何使用索引。在实践中,索引方法需要其他支持例程才能工作。例如,B 树索引方法必须能够比较两个键并确定其中一个是否大于、等于或小于另一个。类似地,哈希索引方法必须能够计算键值的哈希码。这些运算与 SQL 命令中的限定符中使用的操作符无关;它们是索引方法在内部使用的管理例程。

和策略一样,操作符类会确定哪些特定函数对于给定数据类型和语义解释应执行这些角色。索引方法定义它需要的一组函数,并且操作符类通过将它们分配给索引方法指定的“辅助函数数”来标识要使用的正确函数。

此外,一些操作符类允许用户指定控制其行为的参数。每个内置索引访问方法都有可选的“options”辅助函数,用于定义一组操作符类特定的参数。

B 树需要一个比较辅助函数,并允许在操作符类作者选项中提供另外四个辅助函数,如 表 36.9 所示。这些辅助函数的要求在 第 64.1.3 节 中作了进一步解释。

表 36.9。B 树辅助函数

函数 辅助号
比较两个键并返回小于零、零或大于零的整数,表示第一个键是否小于、等于或大于第二个键 1
返回 C 可调用排序辅助函数(可选)的地址 2
将测试值与基值加上/减去偏移量进行比较,并根据比较结果返回 true 或 false(可选) 3
确定使用操作符类的索引是否可以安全地应用 btree 去重优化(可选) 4
定义特定于此操作符类的选项(可选) 5

哈希索引需要一个辅助函数,并且允许在操作符类作者选项中提供另外两个辅助函数,如 表 36.10 所示。

表 36.10。哈希辅助函数

函数 辅助号
计算键的 32 位哈希值 1
计算给定 64 位 salt 的键的 64 位哈希值;如果 salt 为 0,则结果的低 32 位必须匹配函数 1 计算的值(可选) 2
定义特定于此操作符类的选项(可选) 3

GiST 索引具有 11 个辅助函数,其中 6 个是可选的,如 表 36.11 所示。(有关更多信息,请参阅 第 64.2 节。)

表 36.11。GiST 辅助函数

函数 描述 辅助号
一致 确定键是否满足查询限定符 1
联合 计算一组键的联合 2
压缩 计算要索引的键或值的压缩表示(可选) 3
解压缩 计算压缩键的解压缩表示(可选) 4
罚款 计算将新键插入到具有给定子树键的子树中的惩罚 5
picksplit 确定页面的哪些条目要移动到新页面,并计算结果页面的联合键 6
相同 比较两个键,如果它们相等则返回 true 7
距离 确定从键到查询值(可选)的距离 8
获取 计算仅索引扫描的压缩键的原始表示形式(可选) 9
选项 定义特定于此运算符类别的选项(可选) 10
sortsupport 提供在快速索引构建中要使用的排序比较器(可选) 11

SP-GiST 索引具有六个支持函数,其中一个可选,如 表 36.12 所示。(有关更多信息,请参见 第 64.3 节。)

表 36.12。SP-GiST 支持函数

函数 描述 辅助号
config 提供有关运算符类别的基本信息 1
choose 确定如何向内部元组中插入新值 2
picksplit 确定如何对一组值进行分区 3
inner_consistent 确定需要针对查询搜索哪些子分区 4
leaf_consistent 确定键是否满足查询限定符 5
选项 定义特定于此运算符类别的选项(可选) 6

GIN 索引具有七个支持函数,其中四个可选,如 表 36.13 所示。(有关更多信息,请参见 第 64.4 节。)

表 36.13。GIN 支持函数

函数 描述 辅助号
compare 比较两个键并返回小于零、零或大于零的整数,指示第一个键是小于、等于还是大于第二个键 1
extractValue 从要编入索引的值中提取键 2
extractQuery 从查询条件中提取键 3
一致 确定值是否与查询条件匹配(布尔变量)(如果存在支持函数 6 则可选) 4
comparePartial 比较查询中的部分键和索引中的键,并返回小于零、零或大于零的整数,指示 GIN 应忽略此索引项、将该项作为匹配项对待或停止索引扫描(可选) 5
triConsistent 确定值是否与查询条件匹配(三值变量)(如果存在支持函数 4 则可选) 6
选项 定义特定于此运算符类别的选项(可选) 7

BRIN 索引有五个基本支持函数,其中一个函数是可选的,如 表 36.14 中所示。某些版本的函数需要提供附加支持函数。(有关更多信息,请参阅 第 64.5.3 节。)

表 36.14. BRIN 支持函数

函数 描述 辅助号
opcInfo 返回描述索引列的摘要数据的内部信息 1
add_value 将新值添加到现有的摘要索引元组 2
一致 确定值是否与查询条件匹配 3
联合 计算两个摘要元组的并集 4
选项 定义特定于此运算符类别的选项(可选) 5

与搜索运算符不同,支持函数返回特定索引方法所期望的任何数据类型;例如,在 B 树的比较函数中,返回带符号整数。每个支持函数的参数数量和类型同样取决于索引方法。对于 B 树和散列,比较和散列支持函数采用与运算符类中包含的运算符相同输入数据类型,但大多数 GiST、SP-GiST、GIN 和 BRIN 支持函数则不是这种情况。

36.16.4 示例 #

现在,我们已了解这些思路,接下来是创建新的运算符类的示例。(可以在源分发包中的 src/tutorial/complex.csrc/tutorial/complex.sql 中找到此示例的可运作副本。)运算符类封装用于按绝对值顺序对复数进行排序的运算符,因此我们选择名称 complex_abs_ops。首先,我们需要一组运算符。有关定义运算符的过程,请参阅 第 36.14 节 中的讨论。对于 B 树上的运算符类,我们需要以下运算符:

  • 绝对值小于(策略 1)
  • 绝对值小于或等于(策略 2)
  • 绝对值相等(策略 3)
  • 绝对值大于或等于(策略 4)
  • 绝对值大于(策略 5)

定义比较运算符关联集时最不容易出现错误的方式是先编写 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

36.16.5. 运算符类和运算符族 #

到目前为止,我们隐含地假设一个操作符类仅处理一种数据类型。虽然一个特定的索引列中当然只能有一种数据类型,但对索引运算会更有用,将一个索引列与不同数据类型的值进行比较。此外,如果需要联合数据类型操作符与操作符类一起使用,那么经常会出现其他数据类型具有其自己的相关操作符类的情况。明确相关类之间的联系很有用,因为它可以帮助计划程序优化 SQL 查询(特别适用于 B 树操作符类,因为计划程序包含大量关于如何使用它们的信息)。

为满足这些需求,PostgreSQL 使用操作符系列的概念。操作符系列包含一个或多个操作符类,还可以包含可索引操作符以及属于整个系列而不属于系列内任何一个类的对应支持函数。我们称此类操作符和函数在系列中是松散的,而不是绑定到一个特定的类。通常每个操作符类包含单数据类型操作符,而跨数据类型操作符则在系列中是松散的。

操作符系列中的所有操作符和函数都必须具有兼容语义,其中兼容性要求由索引方法设定。因此你可能觉得奇怪,为什么还要将系列中的特定子集挑出来作为操作符类;事实上,出于许多目的,类划分是无关紧要的,而系列是唯一有意义的分组。定义操作符类的目的是规定为了支持任何一个特定索引需要系列的多少内容。如果存在使用一个操作符类的索引,那么就不能删除索引而不删除该操作符类——但这不会影响操作符系列的其他部分,即其他操作符类和松散操作符。因此,应当指定操作符类以包含合理所需的最小操作符和函数集,使之能够处理特定数据类型上的索引,然后可将相关但非必要的操作符添加为操作符系列的松散成员。

例如,PostgreSQL 具有内置 B 树算子族 integer_ops,其中包括算子类 int8_opsint4_opsint2_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 树算子族中,按照 第 64.1.2 节 中的详细说明,该族中的所有算子都必须按兼容的方式排序。该族中的每个算子都必须具有一个支持函数,该支持函数具有与算子相同两个输入数据类型。建议使用一个完整的族,即对于每个数据类型组合,都包含所有运算符。每个运算符类都应仅包含非跨类型运算符和针对其数据类型的支持函数。

为了构建多数据类型哈希运算符族,必须为该族所支持的每种数据类型创建兼容哈希支持函数。这里的兼容性意味着,对于由该族的相等性运算符视为相等的所有两个值,即使这些值属于不同类型,这些函数也保证返回相同的哈希码。如果类型具有不同的物理表示方式,则通常难以实现此目标,但在某些情况下可以实现。此外,将一种数据类型(在运算符族中表示为)转换为另一种数据类型(也在运算符族中表示为)通过隐式或二进制强制转换转换不得更改计算出的哈希值。请注意,每个数据类型只有一个支持函数,而不是每个相等性运算符都有一个支持函数。建议使用一个完整的族,即提供每个数据类型组合的相等性运算符。每个运算符类都应仅包含非跨类型相等性运算符和针对其数据类型的支持函数。

GiST、SP-GiST 和 GIN 索引没有任何明确的跨数据类型操作概念。所支持的运算符集只是给定运算符类的主要支持函数可以处理的任何内容。

在 BRIN 中,要求取决于提供操作符类的框架。对于基于 minmax 的操作符类,所需行为与 B 树操作符族相同:族中所有操作符均必须兼容排序,且强制转换不会改变相关排序顺序。

注意

PostgreSQL 8.3 之前,没有操作符族的概念,因此任何旨在与索引一起使用的跨数据类型操作符都必须直接绑定到索引的操作符类。虽然此方法仍然有效,但不推荐使用,因为它会使索引的依赖性变得太宽,并且当两种数据类型在同一操作符族中具有操作符时,计划程序可以更有效地处理跨数据类型比较。

36.16.6. 操作符类上的系统依赖性 #

PostgreSQL 使用操作符类来推断操作符的属性,这不仅在于它们是否可以与索引一起使用。因此,即使你无意为任何数据类型列编制索引,你可能也希望创建操作符类。

特别是,ORDER BYDISTINCT 等 SQL 特性需要比较和排序值。为了在用户定义的数据类型上实现这些特性,PostgreSQL 查找数据类型的默认 B 树操作符类。此操作符类的 equals 成员定义了系统对 GROUP BYDISTINCT 值相等性的概念,该操作符类施加的排序顺序定义了默认 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 选项。

另一个重要要点是在哈希操作器系列中出现的相等操作器是哈希联接、哈希聚合和相关优化的候选。

36.16.7. 排序操作器 #

部分索引访问方法(当前仅 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;

找到最接近给定目标点的十个地点。Location 列中的 GiST 索引能够高效地执行此操作,因为<->是排序操作符。

尽管搜索操作符必须返回布尔结果,但排序操作符通常会返回其他类型,例如对于距离来说,返回浮点数或数字。此类型通常与被索引的数据类型不同。为避免对不同数据类型的行为做出硬性假设,需要求排序操作符定义来命名 B 树操作符族,该操作符族指定结果数据类型的排序顺序。如上一部分所述,B 树操作符族定义了 PostgreSQL 的排序概念,因此这是一个自然的表示形式。由于点<->操作符返回float8,因此可以在类似于这样的操作符类创建命令中指定该操作符

OPERATOR 15    <-> (point, point) FOR ORDER BY float_ops

其中float_ops是内置操作符族(包括对float8的运算)。此声明指出,该索引能够按<->操作符值得递增顺序返回行。

36.16.8. 操作符类的特殊功能 #

操作符类有两个特殊功能,我们尚未讨论,主要是因为它们不太常与最常用的索引方法一起使用。

正常情况下,将一个操作符声明为操作符类(或族)的成员意味着索引方法能够确切检索满足使用该操作符的WHERE条件的那些行集。例如

SELECT * FROM table WHERE integer_column < 4;

B 树索引能完全满足整数列的索引。但在某些情况下,索引作为匹配行的模糊指南也十分有用。例如,如果 GiST 索引只为几何对象存储包围盒,那么它不能完全满足测试多边形等非矩形对象之间重叠情况的 WHERE 条件。然而,我们可以使用索引查找与其包围盒与目标对象包围盒重叠的对象,然后只对通过索引找到的对象执行精确重叠测试。如果此情景适用,则称该索引对于该运算符为 有损。有损索引搜索通过让索引方法在某行可能或可能不会真正满足查询条件时返回一个 重新检查 标志来实现。然后,核心系统将对此检索到的行执行原始查询条件,以查看其是否应返回作为有效匹配。如果索引保证返回所有必需的行以及一些可以通过执行原始运算符调用来消除的其他行,则此方法有效。支持有损搜索的索引方法(目前为 GiST、SP-GiST 和 GIN)允许各个运算符类的支持函数设置重新检查标志,因此这本质上是一个运算符类特性。

再次考虑我们在索引中只存储多边形等复杂对象的包围盒的情况。在这种情况下,在索引项中存储整个多边形并没有多大价值 — 我们不妨只存储类型为 box 的简单对象。此情况由 STORAGE 选项在 CREATE OPERATOR CLASS 中体现:我们可以编写类似于以下的内容

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

目前,只有 GiST、SP-GiST、GIN 和 BRIN 索引方法支持与列数据类型不同的 STORAGE 类型。当使用 STORAGE 时,GiST compressdecompress 支持例程必须处理数据类型的转换。SP-GiST 同样需要 compress 支持函数来转换到存储类型(当它们之间不同);如果一个 SP-GiST 运算符类也支持检索数据,则反向转换必须由 consistent 函数处理。在 GIN 中,STORAGE 类型识别 key 值的类型,这通常不同于所索引列的类型 - 例如,一个针对整数数组列的运算符类可能具有仅为整数的键。GIN extractValueextractQuery 支持例程负责从索引值中提取键。BRIN 类似于 GIN:STORAGE 类型识别存储的摘要值的类型,而运算符类的支持过程负责正确解释摘要值。