38.15. 连接索引扩展

到目前为止描述的过程使您可以定义新的类型,新的函数和新的运算符。但是,我们尚不能在新数据类型的列上定义索引。为此,我们必须为新数据类型定义一个* operator class *。在本节的稍后部分,我们将以一个示例说明该概念:B 树索引方法的新运算符类,该运算符类以绝对值升序存储和排序复数。

可以将运算符类分组为*“运算符族” *,以显示语义兼容的类之间的关系。当仅涉及一种数据类型时,一个操作符类别就足够了,因此我们将首先关注这种情况,然后再返回到操作符族。

38 .15.1. 索引方法和运算符类

pg_am表的每个索引方法(内部称为访问方法)都包含一行。 PostgreSQL 内置了对常规表访问的支持,但是所有索引方法都在pg_am中进行了描述。可以通过编写必要的代码,然后在pg_am中创建一个条目来添加新的索引访问方法,但这超出了本章的范围(请参阅Chapter 61)。

索引方法的例程不直接了解有关索引方法将要操作的数据类型的任何信息。而是由* operator class * 标识索引方法用于处理特定数据类型所需的一组操作。之所以称为运算符类,是因为它们指定的一件事是可与索引一起使用的WHERE子句运算符集(即可以转换为索引扫描限定符)。运算符类还可以指定索引方法的内部操作所需的某些支持函数,但并不直接对应于可与索引一起使用的任何_3-子句运算符。

可以为同一数据类型和索引方法定义多个运算符类。通过这样做,可以为单个数据类型定义多组索引语义。例如,一个 B 树索引要求为其工作的每种数据类型定义一个排序 Sequences。对于复数数据类型,让一个 B 树运算符类对数据进行复数绝对值排序,对另一个数据按实数部分进行排序,以此类推,可能很有用。通常,运算符类别之一将被认为是最常用的,并将被标记为该数据类型和索引方法的默认运算符类别。

相同的运算符类别名称可以用于几种不同的索引方法(例如,B 树和哈希索引方法都具有名为int4_ops的运算符类别),但是每个这样的类别都是独立的实体,必须分别定义。

38 .15.2. 索引方法策略

与操作员类别关联的操作员由“策略编号”标识,该策略编号用于识别每个操作员在其操作员类别的上下文中的语义。例如,B 树对键施加严格的 Sequences,从小到大,因此对于 B 树,像“小于”和“大于或等于”这样的运算符很有趣。由于 PostgreSQL 允许用户定义运算符,因此 PostgreSQL 无法查看运算符的名称(例如<>=),也无法确定它的比较类型。相反,索引方法定义了一组“策略”,可以将其视为广义运算符。每个运算符类别指定针对特定数据类型和索引语义解释的每个策略对应的实际运算符。

B 树索引方法定义了五个策略,如Table 38.2所示。

表 38.2. B 树策略

OperationStrategy Number
less than1
小于或等于2
equal3
大于或等于4
greater than5

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

表 38.3. 哈希策略

OperationStrategy Number
equal1

GiST 索引更灵活:它们根本没有固定的策略集。取而代之的是,每个特定 GiST 运算符类的“一致性”支持例程都会按喜好解释策略编号。例如,几个内置的 GiST 索引运算符类对二维几何对象进行索引,从而提供Table 38.4中所示的“ R 树”策略。其中有四个是 true 的二维测试(重叠,相同,包含,包含由);其中四个仅考虑 X 方向;其他四个在 Y 方向上提供相同的测试。

表 38.4. GiST 二维“ R 树”策略

OperationStrategy Number
严格离开1
不延伸到2
overlaps3
不延伸到4
严格的权利5
same6
contains7
contained by8
不超过9
strictly below10
strictly above11
不延伸到下方12

SP-GiST 索引的灵 Active 类似于 GiST 索引:它们没有固定的策略集。相反,每个操作员类别的支持例程都根据操作员类别的定义来解释策略编号。例如,内置运算符类对点使用的策略编号显示在Table 38.5中。

表 38.5. SP-GiSTIntegration 策略

OperationStrategy Number
严格离开1
严格的权利5
same6
contained by8
strictly below10
strictly above11

GIN 索引与 GiST 和 SP-GiST 索引相似,因为它们也没有固定的策略集。相反,每个操作员类别的支持例程都根据操作员类别的定义来解释策略编号。例如,内置运算符类用于数组的策略编号显示在Table 38.6中。

表 38.6. GIN 阵列策略

OperationStrategy Number
overlap1
contains2
包含在3
equal4

BRIN 索引与 GiST,SP-GiST 和 GIN 索引相似,因为它们也没有固定的策略集。相反,每个操作员类别的支持例程都根据操作员类别的定义来解释策略编号。例如,内置Minmax运算符类使用的策略编号显示在Table 38.7中。

表 38.7. BRIN Minmax 策略

OperationStrategy Number
less than1
小于或等于2
equal3
大于或等于4
greater than5

请注意,上面列出的所有运算符均返回布尔值。实际上,所有定义为索引方法搜索运算符的运算符都必须返回boolean类型,因为它们必须出现在WHERE子句的顶层才能与索引一起使用。 (某些索引访问方法还支持排序运算符,这些运算符通常不返回布尔值;该功能在Section 38.15.7中进行了讨论。)

38 .15.3. 索引方法支持例程

策略通常不足以让系统确定如何使用索引。实际上,索引方法需要附加的支持例程才能起作用。例如,B 树索引方法必须能够比较两个键并确定一个键是否大于,等于或小于另一个键。同样,哈希索引方法必须能够计算键值的哈希码。这些操作与 SQL 命令中限定词中使用的运算符不对应;它们是索引方法在内部使用的 Management 例程。

与策略一样,操作员类可以确定对于给定的数据类型和语义解释,哪些特定功能应扮演这些角色中的每一个。索引方法定义了所需的功能集,操作员类通过将正确的功能分配给索引方法指定的“支持功能编号”来标识要使用的正确功能。

B 树需要比较支持功能,并允许由操作员类作者选择提供两个附加的支持功能,如Table 38.8所示。这些支持功能的要求在Section 63.3中进一步说明。

表 38.8. B 树支持功能

FunctionSupport Number
比较两个键并返回小于零,零或大于零的整数,指示第一个键是否小于,等于或大于第二个键1
返回 C 调用排序支持函数的地址(可选)2
将测试值与基准值加上/减去偏移量进行比较,并根据比较结果返回 true 或 false(可选)3

哈希索引需要一种支持功能,并允许由操作员类作者选择提供另一种支持功能,如Table 38.9所示。

表 38.9. 哈希支持功能

FunctionSupport Number
计算密钥的 32 位哈希值1
在给定 64 位盐的情况下,计算密钥的 64 位哈希值;如果 salt 为 0,则结果的低 32 位必须与函数 1 计算出的值匹配(可选)2

GiST 索引具有九个支持功能,其中两个是可选的,如Table 38.10所示。 (有关更多信息,请参见Chapter 64。)

表 38.10. GiST 支持功能

FunctionDescriptionSupport Number
consistent确定键是否满足查询限定符1
union计算一组键的并集2
compress计算要编制索引的键或值的压缩表示形式3
decompress计算压缩密钥的解压缩表示4
penalty计算给定子树的密钥将新密钥插入子树的代价5
picksplit确定页面的哪些条目要移动到新页面,并计算结果页面的联合键6
equal比较两个键,如果相等则返回 true7
distance确定键到查询值的距离(可选)8
fetch计算仅索引扫描的压缩键的原始表示形式(可选)9

SP-GiST 索引需要五个支持功能,如Table 38.11所示。 (有关更多信息,请参见Chapter 65。)

表 38.11. SP-GiST 支持功能

FunctionDescriptionSupport Number
config提供有关操作员类别的基本信息1
choose确定如何将新值插入内部 Tuples2
picksplit确定如何划分一组值3
inner_consistent确定需要搜索哪个子分区的查询4
leaf_consistent确定键是否满足查询限定符5

GIN 索引具有六个支持功能,其中三个是可选的,如Table 38.12所示。 (有关更多信息,请参见Chapter 66。)

表 38.12. GIN 支持功能

FunctionDescriptionSupport Number
compare比较两个键并返回小于零,零或大于零的整数,指示第一个键是否小于,等于或大于第二个键1
extractValue从要索引的值中提取键2
extractQuery从查询条件中提取键3
consistent确定值是否与查询条件匹配(布尔变量)(如果存在支持功能 6,则为可选)4
comparePartial比较查询中的部分键和索引中的键,并返回小于零,零或大于零的整数,指示 GIN 是应忽略此索引条目,将条目视为匹配项还是停止索引扫描(可选)5
triConsistent确定值是否与查询条件匹配(三元变体)(如果存在支持功能 4,则为可选)6

BRIN 索引具有四个基本支持功能,如Table 38.13所示;这些基本功能可能需要提供其他支持功能。 (有关更多信息,请参见Section 67.3。)

表 38.13. BRIN 支持功能

FunctionDescriptionSupport Number
opcInfo返回描述索引列的摘要数据的内部信息1
add_value向现有的摘要索引 Tuples 添加新值2
consistent确定值是否符合查询条件3
union计算两个汇总 Tuples 的并集4

与搜索运算符不同,支持函数返回特定索引方法期望的任何数据类型。例如,对于 B 树的比较函数,则为有符号整数。每个支持函数的参数的数量和类型同样取决于 index 方法。对于 B 树和哈希,比较和哈希支持功能采用与运算符类中包含的运算符相同的 Importing 数据类型,但对于大多数 GiST,SP-GiST,GIN 和 BRIN 支持功能而言并非如此。

38 .15.4. 一个例子

现在我们已经了解了这些想法,下面是创建新的运算符类的预期示例。 (您可以在源代码发布的src/tutorial/complex.csrc/tutorial/complex.sql中找到此示例的工作副本.)operator 类封装了按绝对值 Sequences 对复数进行排序的运算符,因此我们选择名称complex_abs_ops。首先,我们需要一组运算符。 Section 38.13中讨论了定义运算符的过程。对于 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 函数名称中包括数据类型名称是一种好习惯,以免与其他数据类型的函数冲突。

  • 我们可以使函数abs_eq的 SQL 名称依赖于 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即可。

38 .15.5. 运算符类别和运算符家族

到目前为止,我们已经隐式假定操作符类仅处理一种数据类型。虽然特定索引列中肯定只能有一种数据类型,但是将索引列与另一数据类型的值进行比较的索引操作通常很有用。同样,如果将跨数据类型的运算符与运算符类别结合使用,则通常其他数据类型具有自己的相关运算符类别。使相关类之间的连接明确是很有帮助的,因为这可以帮助计划者优化 SQL 查询(尤其是对于 B 树操作符类,因为计划者包含大量有关如何使用它们的知识)。

为了满足这些需求,PostgreSQL 使用* operator family * 的概念。一个运算符家族包含一个或多个运算符类,还可以包含属于整个家族而不是该家族中任何单个类的可索引运算符和相应的支持功能。我们说这样的运算符和函数在家庭中是“松散的”,而不是绑定到特定的类中。通常,每个运算符类都包含单个数据类型的运算符,而跨数据类型的运算符在该系列中比较松散。

运算符族中的所有运算符和功能必须具有兼容的语义,其中兼容性要求是由 index 方法设置的。因此,您可能想知道为什么要烦扰地选择该家族的特定子集作为操作员类?实际上,出于许多目的,类划分是无关紧要的,而家庭是唯一有趣的分组。定义运算符类别的原因是,它们指定支持任何特定索引需要多少族。如果存在使用运算符类的索引,则不能在不删除索引的情况下删除该运算符类,但是可以删除运算符家族的其他部分,即其他运算符类和松散的运算符。因此,应指定一个运算符类,以包含使用特定数据类型上的索引合理需要的最少一组运算符和函数,然后可以将相关但非必要的运算符添加为运算符系列的松散成员。 。

例如,PostgreSQL 具有内置的 B 树运算符家族integer_ops,该家族包含bigint(int8),integer(int4)和smallint(int2)列上的索引的运算符类int8_opsint4_opsint2_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) ;

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) ;

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) ;

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) ;

请注意,此定义“超载”了操作员策略和支持功能编号:每个编号在系列中出现多次。只要特定编号的每个实例具有不同的 Importing 数据类型,就可以这样做。两种 Importing 类型都等于操作员类的 Importing 类型的实例是该操作员类的主要操作员和支持功能,并且在大多数情况下,应将其声明为操作员类的一部分,而不是该家族的松散成员。

在 B 树操作员族中,该族中的所有操作员必须按照Section 63.2的详细说明进行兼容排序。对于家庭中的每个操作员,必须有一个支持功能,该功能必须具有与操作员相同的两种 Importing 数据类型。建议一个完整的族,即,对于每种数据类型组合,应包括所有运算符。每个运算符类都应仅包括非交叉类型的运算符并为其数据类型提供支持功能。

要构建多数据类型的哈希运算符系列,必须为该系列支持的每种数据类型创建兼容的哈希支持功能。在这里,兼容性意味着对于函数族的相等运算符认为相等的任何两个值,即使值的类型不同,也可以保证函数返回相同的哈希码。当类型具有不同的物理表示形式时,通常很难做到这一点,但是在某些情况下可以做到。此外,通过隐式或二进制强制转换将值从运算符家族中表示的一种数据类型转换为也在运算符家族中表示的另一种数据类型,不得更改所计算的哈希值。请注意,每种数据类型只有一个支持功能,而每个相等运算符没有一个。建议一个族完整,即为每种数据类型组合提供一个相等运算符。每个运算符类都应仅包括非交叉类型的相等运算符及其数据类型的支持函数。

GiST,SP-GiST 和 GIN 索引没有任何跨数据类型操作的明确概念。对于给定的操作员类别,支持的操作员集合就是其主要支持功能可以处理的任何事情。

在 BRIN 中,要求取决于提供操作员类别的框架。对于基于minmax的运算符类,所需的行为与 B 树运算符族相同:该族中的所有运算符必须兼容地排序,并且强制类型转换不能更改关联的排序 Sequences。

Note

在 PostgreSQL 8.3 之前,没有运算符族的概念,因此任何打算与索引一起使用的跨数据类型运算符都必须直接绑定到索引的运算符类中。尽管此方法仍然有效,但已弃用该方法,因为它使索引的依赖性过大,并且当两种数据类型的操作符在同一操作符族中时,计划程序可以更有效地处理跨数据类型的比较。

38 .15.6. 系统对操作员类别的依赖

PostgreSQL 使用运算符类以更多方式推断运算符的属性,而不仅仅是它们是否可以与索引一起使用。因此,即使您无意为数据类型的任何列构建索引,也可能要创建运算符类。

特别是,有些 SQL 功能(例如ORDER BYDISTINCT)需要对值进行比较和排序。为了在用户定义的数据类型上实现这些功能,PostgreSQL 寻找该数据类型的默认 B 树操作符类。此运算符类的“等于”成员定义GROUP BYDISTINCT的值相等的系统概念,并且运算符类施加的排序 Sequences 定义了默认的ORDER BY排序。

如果数据类型没有默认的 B 树操作符类,则系统将查找默认的哈希操作符类。但是由于这种运算符类仅提供相等性,因此它仅能够支持分组而不是排序。

如果数据类型没有默认的运算符类,则尝试将这些 SQL 功能与数据类型一起使用时,会出现诸如“无法识别排序运算符”之类的错误。

Note

在 7.4 之前的 PostgreSQL 版本中,排序和分组操作将隐式使用名为=<>的运算符。依赖默认运算符类的新行为避免了必须对具有特定名称的运算符的行为进行任何假设。

例如,可以通过在USING选项中指定该类的小于运算符来按非默认 B 树运算符类别进行排序。

SELECT * FROM mytable ORDER BY somecol USING ~<~;

或者,在USING中指定该类的大于运算符会选择一个降序排序。

用户定义类型的数组的比较还依赖于该类型的默认 B-tree 运算符类定义的语义。如果没有默认的 B-tree 运算符类,但是有默认的哈希运算符类,则支持数组相等,但不支持排序比较。

需要更多特定于数据类型的知识的另一个 SQL 功能是窗口函数的RANGE * offset * PRECEDING/FOLLOWING框架选项(请参见Section 4.2.8)。对于诸如

SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
  FROM mytable;

仅仅知道x如何 Order 是不够的;数据库还必须了解如何对当前行的x值“减 5”或“加 10”,以标识当前窗口框架的边界。使用定义ORDER BY排序的 B 树运算符类提供的比较运算符,可以将结果边界与x的其他行的值进行比较-但加法和减法运算符不是运算符类的一部分,因此应使用哪个运算符?硬选择该选项是不希望的,因为不同的排序 Sequences(不同的 B-tree 运算符类)可能需要不同的行为。因此,B 树运算符类可以指定一个* in_range *支持函数,该函数封装对其排序 Sequences 有意义的加法和减法行为。如果有多种数据类型可以用作RANGE子句中的偏移量,它甚至可以提供不止一种 in_range 支持功能。如果与窗口的ORDER BY子句关联的 B 树操作符类不具有匹配的 in_range 支持功能,则不支持RANGE * offset * PRECEDING/FOLLOWING选项。

另一个重要的一点是,出现在哈希运算符族中的相等运算符是哈希联接,哈希聚合和相关优化的候选者。哈希运算符系列在这里至关重要,因为它标识要使用的哈希函数。

38 .15.7. Order 运算符

某些索引访问方法(目前仅 GiST)支持排序运算符的概念。到目前为止,我们一直在讨论的是搜索运算符。搜索运算符是一种可以搜索索引以找到满足WHERE * indexed_column * * operator * * constant *的行的运算符。请注意,对于匹配行的返回 Sequences,没有任何承诺。相反,排序运算符并不限制可以返回的行的集合,而是确定其 Sequences。排序运算符是一种可以对其扫描的索引,以按ORDER BY * indexed_column * * operator * * constant *表示的 Sequences 返回行。以这种方式定义排序运算符的原因是,如果运算符是一种用于度量距离的运算符,则它支持最近邻搜索。例如,类似

SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;

查找最接近给定目标点的十个位置。 location_1 列上的 GiST 索引可以有效地做到这一点,因为<->是 Order 运算符。

虽然搜索运算符必须返回布尔结果,但排序运算符通常会返回其他一些类型,例如 float 或用于距离的数字。此类型通常与被索引的数据类型不同。为了避免对不同数据类型的行为进行严格的假设,需要使用排序运算符的定义来命名 B 树运算符系列,该系列指定结果数据类型的排序 Sequences。如前一节所述,B 树操作符系列定义了 PostgreSQL 的 Sequences 概念,因此这是自然的表示。由于点<->运算符返回float8,因此可以在运算符类创建命令中指定该点,如下所示:

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

其中float_ops是内置运算符系列,其中包括对float8的操作。该声明指出索引能够按<->运算符的值递增 Sequences 返回行。

38 .15.8. 操作员类别的特殊功能

我们尚未讨论运算符类的两个特殊功能,主要是因为它们对于最常用的索引方法没有用。

通常,将操作符声明为操作符类(或族)的成员意味着索引方法可以使用该操作符精确检索满足WHERE条件的行集。例如:

SELECT * FROM table WHERE integer_column < 4;

可以通过整数列上的 B 树索引完全满足。但是在某些情况下,索引可用作匹配行的不精确指南。例如,如果 GiST 索引仅存储几何对象的边界框,则它不能完全满足WHERE条件,该条件测试非矩形对象(例如多边形)之间的重叠。但是,我们可以使用索引查找边界框与目标对象的边界框重叠的对象,然后仅对索引找到的对象进行精确的重叠测试。如果适用这种情况,则该索引对于操作员而言称为“有损”。有损索引搜索是通过使索引方法在一行可能会或可能不会 true 满足查询条件时返回* recheck *标志来实现的。然后,核心系统将在检索到的行上测试原始查询条件,以查看是否应将其作为有效匹配返回。如果保证索引返回所有必需的行,再加上一些其他行(可以通过执行原始运算符调用来消除),则此方法有效。支持有损搜索的索引方法(当前为 GiST,SP-GiST 和 GIN)允许各个操作员类别的支持功能设置重新检查标志,因此,这本质上是操作员类别的功能。

再次考虑以下情况:我们仅在索引中存储复杂对象(例如多边形)的边界框。在这种情况下,将整个多边形存储在索引条目中没有太大价值-我们最好只存储一个简单的box类型的对象。这种情况由CREATE OPERATOR CLASS中的STORAGE选项表示:我们可以这样写:

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

目前,只有 GiST,GIN 和 BRIN 索引方法支持与列数据类型不同的STORAGE类型。当使用STORAGE时,GiST compressdecompress支持例程必须处理数据类型转换。在 GIN 中,STORAGE类型标识“键”值的类型,该值通常与索引列的类型不同-例如,整数数组列的运算符类可能具有仅是整数的键。 GIN extractValueextractQuery支持例程负责从索引值中提取键。 BRIN 与 GIN 相似:STORAGE类型标识存储的摘要值的类型,并且操作员类的支持过程负责正确解释摘要值。