范围类型是表示某种元素类型(称为范围的子类型)的一系列值的范围的数据类型。例如,timestamp
的范围可以用来表示会议室被预定的时间范围。在这种情况下,数据类型为 tsrange
( “时间戳范围”的缩写),而 timestamp
是子类型。子类型必须具有全序,以便很好地定义元素值是否在值的范围内、之前或之后。
范围类型很有用,因为它们在单个范围值中表示许多元素值,并且可以清楚地表达诸如重叠范围之类的概念。将时间和日期范围用于调度目的就是一个最明显的例子;但是价格范围、仪器的测量范围等等也可能很有用。
每个范围类型都有一个对应的多重范围类型。多重范围是非连续、非空、非空的有序范围列表。大多数范围运算符也适用于多重范围,并且它们自己有一些函数。
PostgreSQL 带有以下内建范围类型
int4range
— integer
的范围,int4multirange
— 对应的多重范围
int8range
— bigint
的范围,int8multirange
— 对应的多重范围
numrange
— numeric
的范围,nummultirange
— 对应的多重范围
tsrange
— timestamp without time zone
的范围,tsmultirange
— 对应的多重范围
tstzrange
— timestamp with time zone
的范围,tstzmultirange
— 对应的多重范围
daterange
— date
的范围,datemultirange
— 对应的多重范围
此外,您可以定义自己的范围类型;有关更多信息,请参阅CREATE TYPE。
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));
每个非空范围都有两个边界,下边界和上边界。这些值之间的所有点都包含在范围内。包含的边界意味着边界点本身也包含在范围内,而不包含的边界意味着边界点不包含在范围内。
在范围的文本形式中,包含的下边界用 “[
” 表示,而不包含的下边界用 “(
” 表示。同样,包含的上边界用 “]
” 表示,而不包含的上边界用 “)
” 表示。(有关更多详细信息,请参见第 8.17.5 节。)
函数 lower_inc
和 upper_inc
分别测试范围值的下边界和上边界的包含性。
范围的下边界可以省略,这意味着所有小于上边界的值都包含在范围内,例如 (,3]
。同样,如果省略范围的上边界,则所有大于下边界的值都包含在范围内。如果同时省略下边界和上边界,则认为该元素类型的所有值都在范围内。将缺失的边界指定为包含会自动转换为不包含,例如 [,]
将转换为 (,)
。您可以将这些缺失值视为 +/- 无穷大,但它们是特殊的范围类型值,并且被认为超出了任何范围元素类型的 +/- 无穷大值。
具有“无穷大”概念的元素类型可以使用它们作为显式边界值。例如,对于时间戳范围,[today,infinity)
排除特殊 timestamp
值 infinity
,而 [today,infinity]
包括它,[today,)
和 [today,]
也是如此。
函数 lower_inf
和 upper_inf
分别测试范围的无限下边界和上边界。
范围值的输入必须遵循以下模式之一
(lower-bound
,upper-bound
) (lower-bound
,upper-bound
] [lower-bound
,upper-bound
) [lower-bound
,upper-bound
] empty
圆括号或方括号表示下边界和上边界是排除的还是包含的,如前所述。请注意,最后一种模式是 empty
,它表示空范围(不包含任何点的范围)。
下边界
可以是对于子类型有效的字符串输入,也可以为空以表示没有下边界。同样,上边界
可以是对于子类型有效的字符串输入,也可以为空以表示没有上边界。
每个边界值都可以使用 "
(双引号)字符引起来。如果边界值包含圆括号、方括号、逗号、双引号或反斜杠,则这是必要的,因为这些字符将被视为范围语法的一部分。要在带引号的边界值中放置双引号或反斜杠,请在其前面加上反斜杠。(此外,带双引号的边界值中的一对双引号被视为表示双引号字符,类似于 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;
每个范围类型都有一个与范围类型名称相同的构造函数。使用构造函数通常比编写范围文字常量更方便,因为它避免了对边界值进行额外引用的需要。构造函数接受两个或三个参数。两参数形式构造一个标准形式的范围(下边界包含,上边界排除),而三参数形式构造一个边界由第三个参数指定的形式的范围。第三个参数必须是字符串 “()
”、“(]
”、“[)
” 或 “[]
” 之一。例如
-- 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));
离散范围是指其元素类型具有明确“步长”(例如 integer
或 date
)的范围。在这些类型中,当两个元素之间没有有效值时,可以说它们是相邻的。这与连续范围形成对比,在连续范围中,始终(或几乎始终)可以在两个给定值之间识别其他元素值。例如,在 numeric
类型上的范围是连续的,在 timestamp
上的范围也是如此。(即使 timestamp
的精度有限,因此理论上可以将其视为离散的,但最好将其视为连续的,因为步长通常不重要。)
另一种考虑离散范围类型的方式是,对于每个元素值,都有一个清晰的“下一个”或“上一个”值的概念。了解这一点后,可以通过选择下一个或上一个元素值而不是最初给定的值,在范围边界的包含和排除表示之间进行转换。例如,在整数范围类型中,[4,8]
和(3,9)
表示相同的值集;但对于数值范围,情况就并非如此。
离散范围类型应该有一个规范化函数,该函数知道元素类型所需的步长。规范化函数负责将范围类型的等效值转换为具有相同的表示形式,特别是始终一致的包含或排除边界。如果未指定规范化函数,则即使它们在实际中可能表示相同的值集,具有不同格式的范围也将始终被视为不相等。
内置范围类型int4range
、int8range
和daterange
都使用包含下界并排除上界的规范形式;即[)
。但是,用户定义的范围类型可以使用其他约定。
用户可以定义自己的范围类型。这样做最常见的原因是使用内置范围类型中未提供的子类型的范围。例如,要定义一个子类型为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。
可以为范围类型的表列创建 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 树和哈希支持主要是为了允许查询中的内部排序和哈希,而不是创建实际索引。
虽然 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
如果您发现文档中有任何不正确的地方、与您使用特定功能的体验不符或需要进一步澄清的地方,请使用此表单报告文档问题。