2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不受支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2

8.17. 范围类型 #

范围类型是表示某个元素类型(称为范围的子类型)值范围的数据类型。例如,timestamp的范围可用于表示会议室预订的时间范围。在这种情况下,数据类型为tsrange时间戳范围的简称),timestamp是子类型。子类型必须具有全序关系,以便明确定义元素值是在值范围内、之前还是之后。

范围类型很有用,因为它们在一个范围值中表示许多元素值,并且因为可以清楚地表达诸如重叠范围之类的概念。将时间和日期范围用于调度目的是最明显的例子;但是,价格范围、仪器的测量范围等等也可能有用。

每个范围类型都有一个对应多范围类型。多范围是非连续、非空、非空的范围的有序列表。大多数范围运算符也适用于多范围,并且它们有一些自己的函数。

8.17.1. 内置范围和多范围类型 #

PostgreSQL 带有以下内置范围类型

  • int4rangeinteger的范围,int4multirange — 对应的多范围

  • int8rangebigint的范围,int8multirange — 对应的多范围

  • numrangenumeric的范围,nummultirange — 对应的多范围

  • tsrangetimestamp without time zone的范围,tsmultirange — 对应的多范围

  • tstzrangetimestamp with time zone的范围,tstzmultirange — 对应的多范围

  • daterangedate的范围,datemultirange — 对应的多范围

此外,您可以定义自己的范围类型;有关更多信息,请参阅CREATE TYPE

8.17.2. 例子 #

CREATE TABLE reservation (room int, during tsrange);
INSERT INTO reservation VALUES
    (1108, '[2010-01-01 14:30, 2010-01-01 15:30)');

-- Containment
SELECT int4range(10, 20) @> 3;

-- Overlaps
SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);

-- Extract the upper bound
SELECT upper(int8range(15, 25));

-- Compute the intersection
SELECT int4range(10, 20) * int4range(15, 25);

-- Is the range empty?
SELECT isempty(numrange(1, 5));

有关范围类型运算符和函数的完整列表,请参阅表 9.56表 9.58

8.17.3. 包含和排除边界 #

每个非空范围都有两个边界,下界和上界。这些值之间的所有点都包含在范围内。包含边界意味着边界点本身也包含在范围内,而排除边界意味着边界点不包含在范围内。

在范围的文本形式中,包含下界用[表示,而排除下界用(表示。同样,包含上界用]表示,而排除上界用)表示。(有关更多详细信息,请参阅第 8.17.5 节。)

函数lower_incupper_inc分别测试范围值的上下界是否包含。

8.17.4. 无限(无界)范围 #

可以省略范围的下界,这意味着所有小于上界的值都包含在范围内,例如(,3]。同样,如果省略范围的上界,则所有大于下界的值都包含在范围内。如果上下界都被省略,则所有元素类型的值都被认为在范围内。将缺失的边界指定为包含将自动转换为排除,例如[,]将转换为(,)。您可以将这些缺失值视为+/-无穷大,但它们是特殊的范围类型值,并且被认为超出任何范围元素类型的+/-无穷大值。

具有“无穷大”概念的元素类型可以使用它们作为显式边界值。例如,对于时间戳范围,[today,infinity)不包括特殊timestampinfinity,而[today,infinity]则包括它,[today,)[today,]也是如此。

函数lower_infupper_inf分别测试范围的下界和上界是否为无限。

8.17.5. 范围输入/输出 #

范围值的输入必须遵循以下模式之一

(lower-bound,upper-bound)
(lower-bound,upper-bound]
[lower-bound,upper-bound)
[lower-bound,upper-bound]
empty

括号或方括号指示上下界是排除还是包含,如前所述。请注意,最后一个模式是empty,它表示空范围(不包含任何点的范围)。

lower-bound可以是子类型有效输入的字符串,也可以是空字符串以指示没有下界。同样,upper-bound可以是子类型有效输入的字符串,也可以是空字符串以指示没有上界。

每个边界值都可以使用"(双引号)字符进行引用。如果边界值包含括号、方括号、逗号、双引号或反斜杠,则这是必需的,因为这些字符否则会被视为范围语法的一部分。要在带引号的边界值中放置双引号或反斜杠,请在其前面加上反斜杠。(此外,带引号的边界值中的双引号对被视为表示双引号字符,类似于 SQL 文字字符串中单引号的规则。)或者,您可以避免引用并使用反斜杠转义来保护所有否则会被视为范围语法的字符。此外,要编写一个空字符串的边界值,请编写"",因为不写任何内容意味着无限边界。

在范围值的前后允许使用空格,但在括号或方括号之间的任何空格都被视为下界或上界值的一部分。(根据元素类型,它可能是或可能不是重要的。)

注意

这些规则与在复合类型文字中编写字段值的规则非常相似。有关其他说明,请参阅第 8.16.6 节

例子

-- includes 3, does not include 7, and does include all points in between
SELECT '[3,7)'::int4range;

-- does not include either 3 or 7, but includes all points in between
SELECT '(3,7)'::int4range;

-- includes only the single point 4
SELECT '[4,4]'::int4range;

-- includes no points (and will be normalized to 'empty')
SELECT '[4,4)'::int4range;

多范围的输入是大括号({}),其中包含零个或多个有效的范围,用逗号分隔。在括号和逗号周围允许使用空格。这旨在让人想起数组语法,尽管多范围要简单得多:它们只有一维,并且不需要引用其内容。(但是,其范围的边界可以像上面那样引用。)

例子

SELECT '{}'::int4multirange;
SELECT '{[3,7)}'::int4multirange;
SELECT '{[3,7), [8,9)}'::int4multirange;

8.17.6. 构造范围和多范围 #

每个范围类型都有一个与范围类型同名的构造函数。使用构造函数通常比编写范围文字常量更方便,因为它避免了需要额外引用边界值。构造函数接受两个或三个参数。两个参数的形式构造标准形式的范围(下界包含,上界排除),而三个参数的形式构造边界形式由第三个参数指定的范围。第三个参数必须是字符串()(][)[]之一。例如

-- The full form is: lower bound, upper bound, and text argument indicating
-- inclusivity/exclusivity of bounds.
SELECT numrange(1.0, 14.0, '(]');

-- If the third argument is omitted, '[)' is assumed.
SELECT numrange(1.0, 14.0);

-- Although '(]' is specified here, on display the value will be converted to
-- canonical form, since int8range is a discrete range type (see below).
SELECT int8range(1, 14, '(]');

-- Using NULL for either bound causes the range to be unbounded on that side.
SELECT numrange(NULL, 2.2);

每个范围类型也都有一个与多范围类型同名的多范围构造函数。构造函数接受零个或多个参数,这些参数都是适当类型的范围。例如

SELECT nummultirange();
SELECT nummultirange(numrange(1.0, 14.0));
SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));

8.17.7. 离散范围类型 #

离散范围是指其元素类型具有明确定义的“步长”的范围,例如integerdate。在这些类型中,当它们之间没有有效值时,可以认为两个元素是相邻的。这与连续范围形成对比,在连续范围内,始终(或几乎始终)可以识别两个给定值之间的其他元素值。例如,numeric类型的范围是连续的,timestamp类型的范围也是连续的。(即使timestamp具有有限的精度,因此理论上可以被视为离散的,但最好将其视为连续的,因为步长通常不重要。)

考虑离散范围类型的另一种方法是,每个元素值都有一个明确的“下一个”或“上一个”值。了解这一点,可以通过选择下一个或上一个元素值而不是最初给定的元素值,在范围边界的包含和排除表示之间进行转换。例如,在整数范围类型中,[4,8](3,9)表示相同的数值集;但是对于数值范围来说,情况并非如此。

离散范围类型应该有一个规范化函数,该函数知道元素类型的所需步长。规范化函数负责将范围类型的等效值转换为具有相同表示形式,特别是始终包含或排除边界。如果没有指定规范化函数,则即使范围在现实中可能表示相同的数值集,但格式不同的范围也将始终被视为不等。

内置的范围类型 int4rangeint8rangedaterange 都使用规范形式,该形式包含下界并排除上界;即 [)。但是,用户定义的范围类型可以使用其他约定。

8.17.8. 定义新的范围类型 #

用户可以定义自己的范围类型。这样做的最常见原因是在内置范围类型中未提供的子类型上使用范围。例如,要定义一个子类型为 float8 的新范围类型

CREATE TYPE floatrange AS RANGE (
    subtype = float8,
    subtype_diff = float8mi
);

SELECT '[1.234, 5.678]'::floatrange;

因为 float8 没有有意义的 步长,所以我们在此示例中不定义规范化函数。

当您定义自己的范围时,您会自动获得相应的多分区类型。

定义您自己的范围类型还可以允许您指定要使用的不同子类型 B 树操作符类或排序规则,以便更改确定哪些值落入给定范围的排序顺序。

如果子类型被认为具有离散值而不是连续值,则 CREATE TYPE 命令应指定一个 canonical 函数。规范化函数接收一个输入范围值,并且必须返回一个等效的范围值,该值可能具有不同的边界和格式。例如,对于表示相同值的两个范围(例如整数范围 [1, 7][1, 8)),其规范输出必须相同。您选择哪个表示形式作为规范形式并不重要,只要两个格式不同的等效值始终映射到具有相同格式的相同值即可。除了调整包含/排除边界格式之外,规范化函数还可以舍入边界值,以防所需步长大于子类型能够存储的步长。例如,可以将 timestamp 上的范围类型定义为具有一个小时的步长,在这种情况下,规范化函数需要舍入不是小时倍数的边界,或者可能改为抛出错误。

此外,任何旨在与 GiST 或 SP-GiST 索引一起使用的范围类型都应定义一个子类型差异或 subtype_diff 函数。(即使没有 subtype_diff,索引仍然可以工作,但与提供差异函数相比,效率可能会大大降低。)子类型差异函数接收子类型的两个输入值,并返回它们的差值(即,X 减去 Y),表示为 float8 值。在我们上面的示例中,可以使用为常规 float8 减法运算符提供的函数 float8mi;但对于任何其他子类型,都需要进行某种类型转换。可能还需要对如何将差异表示为数字进行一些创造性的思考。在最大程度上,subtype_diff 函数应与所选操作符类和排序规则隐含的排序顺序一致;也就是说,当第一个参数大于第二个参数(根据排序顺序)时,其结果应为正。

一个不太简化的 subtype_diff 函数示例是

CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS
'SELECT EXTRACT(EPOCH FROM (x - y))' LANGUAGE sql STRICT IMMUTABLE;

CREATE TYPE timerange AS RANGE (
    subtype = time,
    subtype_diff = time_subtype_diff
);

SELECT '[11:10, 23:00]'::timerange;

有关创建范围类型的更多信息,请参阅 CREATE TYPE

8.17.9. 索引 #

可以为范围类型的表列创建 GiST 和 SP-GiST 索引。还可以为多分区类型的表列创建 GiST 索引。例如,要创建一个 GiST 索引

CREATE INDEX reservation_idx ON reservation USING GIST (during);

范围上的 GiST 或 SP-GiST 索引可以加速涉及以下范围运算符的查询:=&&<@@><<>>-|-&<&>。多分区上的 GiST 索引可以加速涉及相同多分区运算符集的查询。范围上的 GiST 索引和多分区上的 GiST 索引还可以分别加速涉及这些跨类型范围到多分区和多分区到范围运算符的查询:&&<@@><<>>-|-&<&>。有关更多信息,请参阅 表 9.56

此外,可以为范围类型的表列创建 B 树和哈希索引。对于这些索引类型,基本上唯一有用的范围操作是相等性。为范围值定义了 B 树排序顺序,并带有相应的 <> 运算符,但该排序顺序相当任意,在现实世界中通常没有用。范围类型的 B 树和哈希支持主要用于在查询中内部进行排序和哈希处理,而不是创建实际索引。

8.17.10. 范围上的约束 #

虽然 UNIQUE 是标量值的自然约束,但它通常不适用于范围类型。相反,排除约束通常更合适(请参阅 CREATE TABLE ... CONSTRAINT ... EXCLUDE)。排除约束允许指定诸如 不重叠 之类的范围类型约束。例如

CREATE TABLE reservation (
    during tsrange,
    EXCLUDE USING GIST (during WITH &&)
);

该约束将防止任何重叠值同时存在于表中

INSERT INTO reservation VALUES
    ('[2010-01-01 11:30, 2010-01-01 15:00)');
INSERT 0 1

INSERT INTO reservation VALUES
    ('[2010-01-01 14:45, 2010-01-01 15:45)');
ERROR:  conflicting key value violates exclusion constraint "reservation_during_excl"
DETAIL:  Key (during)=(["2010-01-01 14:45:00","2010-01-01 15:45:00")) conflicts
with existing key (during)=(["2010-01-01 11:30:00","2010-01-01 15:00:00")).

您可以使用 btree_gist 扩展来定义普通标量数据类型的排除约束,然后可以将其与范围排除结合使用以获得最大的灵活性。例如,在安装 btree_gist 后,以下约束仅当会议室编号相等时才会拒绝重叠范围

CREATE EXTENSION btree_gist;
CREATE TABLE room_reservation (
    room text,
    during tsrange,
    EXCLUDE USING GIST (room WITH =, during WITH &&)
);

INSERT INTO room_reservation VALUES
    ('123A', '[2010-01-01 14:00, 2010-01-01 15:00)');
INSERT 0 1

INSERT INTO room_reservation VALUES
    ('123A', '[2010-01-01 14:30, 2010-01-01 15:30)');
ERROR:  conflicting key value violates exclusion constraint "room_reservation_room_during_excl"
DETAIL:  Key (room, during)=(123A, ["2010-01-01 14:30:00","2010-01-01 15:30:00")) conflicts
with existing key (room, during)=(123A, ["2010-01-01 14:00:00","2010-01-01 15:00:00")).

INSERT INTO room_reservation VALUES
    ('123B', '[2010-01-01 14:30, 2010-01-01 15:30)');
INSERT 0 1

提交更正

如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表单 报告文档问题。