范围类型
范围类型是表达某种元素类型值的范围的数据类型,这种元素类型称为称为范围的subtype。例如,timestamp的范围,可用来表达一个会议室被保留的时间范围。在这种情况下,范围的数据类型是tsrange(timestamp range的简写),范围subtype的数据类型为timestamp。subtype必须具有某种总体的顺序,保证元素值是在范围值之内。范围值界限是清楚的。
范围类型可以表达一种单一范围值中的多个元素值,并且可以清晰地表达诸如范围重叠等概念。例如,用于时间安排的时间和日期范围、价格范围、仪器的量程等方面,都可使用范围类型。
1.1.1 内建范围类型
GBase 8c提供如下内建范围类型:
l int4range — integer的范围
l int8range — bigint的范围
l numrange — numeric的范围
l tsrange — 不带时区的 timestamp的范围
l tstzrange — 带时区的 timestamp的范围
l daterange — date的范围
此外,用户也可以定义自己的范围类型,详见14.94 CREATE TYPE。
示例
gbase=# CREATE TABLE reservation (room int, during tsrange);
CREATE TABLE
gbase=# INSERT INTO reservation VALUES (1108, '[2010-01-01 14:30, 2010-01-01 15:30)');
INSERT 0 1
-- 包含
gbase=# SELECT int4range(10, 20) @> 3;
f
-- 重叠
gbase=# SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);
t
-- 抽取上界
gbase=# SELECT upper(int8range(15, 25));
25
-- 计算交集
gbase=# SELECT int4range(10, 20) * int4range(15, 25);
[15,20)
-- 范围为空吗?
gbase=# SELECT isempty(numrange(1, 5));
f
范围类型上的操作符和函数的完整列表,参见5.17 范围函数和操作符。
1.1.2 包含和排除边界
每一个非空范围都有两个界限:下界和上界。上下界之间的所有值都被包括在范围内。包含界限意味着边界点本身也被包括在范围内,而排除边界则意味边界点不被包括在范围内。
在范围的文本形式中,包含下界用”[”表示,排除下界用”(”表示。同理,包含上界用”]”表示,排除上界用”)”表示。详见3.11.4 范围输入/输出。
用户可以通过函数lower_inc和upper_inc,分别测试范围值的上下界。
1.1.3 无限(无界)范围
当忽略范围的下界时,所有小于上界的值都被包括在范围中,例如(,3]。同理,如果忽略范围的上界,那么所有比上界大的值都包括在范围中。如果上下界都被忽略,该元素类型的所有值都包含在该范围中。在GBase 8c数据库中,缺省的包含界限自动转换为排除界限,例如:[ , ]转换为( , )。
具有无限(infinity)概念的元素类型,可以使用infinity作为显式边界值。例如,在时间戳范围,[today,infinity)不包括特殊的timestamp值infinity。如果使用[today,infinity],则包含特殊值infinity。尽管这二者的区别,好比[today, )和[today, ],范围值差别不大。
用户可以使用函数lower_inf和upper_inf,分别测试范围的无限上下界。
1.1.4 范围输入/输出
范围值的输入必须遵循下列模式之一:
(lower-bound,upper-bound)
(lower-bound,upper-bound]
[lower-bound,upper-bound)
[lower-bound,upper-bound]
empty
圆括号或方括号指示上下界是否为被排除或被包含的。empty表示空范围。
lower-bound可以是作为subtype的合法输入的一个字符串,或者是空表示没有下界。同样,upper-bound可以是作为subtype的合法输入的一个字符串,或者是空表示没有上界。
l 每个界限值可以使用"(双引号)字符引用。如果界限值包含圆括号、方括号、逗号、双引号或反斜线时,必须使用双引号。否则那些字符认定为范围语法,而非界限值内容。
l 如需把双引号或反斜线放在被引用的界限值中,则需在前面添加使用一个反斜线。
l 在双引号引用的界限值中的一对双引号,表示为双引号字符,这与SQL字符串中的单引号规则类似。
l 用户可以避免引用,并且使用反斜线转义来保护所有数据字符,否则它们会被当做返回语法的一部分。
l 如需写一个空字符串的界限值,则可以写成""。什么都不写,默认表示一个无限界限。
l 范围值前后允许有空格,但是圆括号或方括号之间的任何空格,认定为上下界值的一部分。
示例
-- 包括 3,不包括 7,并且包括 3 和 7 之间的所有点
gbase=# SELECT '[3,7)'::int4range;
[3,7)
-- 既不包括 3 也不包括 7,但是包括之间的所有点
gbase=# SELECT '(3,7)'::int4range;
[4,7)
-- 只包括单独一个点 4
gbase=# SELECT '[4,4]'::int4range;
[4,5)
-- 不包括点(并且将被标准化为 '空')
gbase=# SELECT '[4,4)'::int4range;
empty
1.1.5 构造范围
每一种范围类型都有一个与其同名的构造器函数。使用构造器函数常常比写一个范围文字常数更方便,因为它避免了对界限值的额外引用。构造器函数接受两个或三个参数。两个参数的形式以标准的形式构造一个范围(下界是包含的,上界是排除的), 而三个参数的形式按照第三个参数指定的界限形式构造一个范围。第三个参数必须是下列字符串之一: “()”、 “(]”、 “[)”或者 “[]”。 例如:
-- 完整形式是:下界、上界以及指示界限包含性/排除性的文本参数。
gbase=# SELECT numrange(1.0, 14.0, '(]');
(1.0,14.0]
-- 如果第三个参数被忽略,则假定为 '[)'。
gbase=# SELECT numrange(1.0, 14.0);
[1.0,14.0)
-- 尽管这里指定了 '(]',显示时该值将被转换成标准形式,因为 int8range 是一种离散范围类型(见下文)。
gbase=# SELECT int8range(1, 14, '(]');
[2,15)
-- 为一个界限使用 NULL 导致范围在那一边是无界的。
gbase=# SELECT numrange(NULL, 2.2);
(,2.2)
1.1.6 离散范围类型
一种范围的元素类型具有一个良定义的“步长”,例如integer或date。在这些类型中,如果两个元素之间没有合法值,它们可以被说成是相邻。这与连续范围相反,连续范围中总是(或者几乎总是)可以在两个给定值之间标识其他元素值。例如, numeric类型之上的一个范围就是连续的,timestamp上的范围也是(尽管timestamp具有有限的精度,并且在理论上可以被当做离散的,最好认为它是连续的,因为通常并不关心它的步长)。
另一种考虑离散范围类型的方法是对每一个元素值都有一种清晰的“下一个”或“上一个”值。了解了这种思想之后,通过选择原来给定的下一个或上一个元素值来取代它,就可以在一个范围界限的包含和排除表达之间转换。例如,在一个整数范围类型中,[4,8]和(3,9)表示相同的值集合,但是对于 numeric 上的范围就不是这样。
一个离散范围类型应该具有一个正规化函数,它知道元素类型期望的步长。正规化函 数负责把范围类型的相等值转换成具有相同的表达,特别是与包含或者排除界限一 致。如果没有指定一个正规化函数,那么具有不同格式的范围将总是会被当作不等, 即使它们实际上是表达相同的一组值。
内建的范围类型int4range、int8range和daterange都使用一种正规的形式,该形式包括下界并且排除上界,也就是[)。不过,用户定义的范围类型可以使用其他习惯。
1.1.7 定义新的范围类型
用户可以定义他们自己的范围类型。这样做最常见的原因是为了使用内建范围类型中没有提供的 subtype 上的范围。例如,要创建一个 subtype float8的范围类型:
gbase=# CREATE TYPE floatrange AS RANGE ( subtype = float8,subtype_diff = float8mi);
CREATE TYPE
gbase=# SELECT '[1.234, 5.678]'::floatrange;
[1.234,5.678]
因为float8没有有意义的“步长”,我们在这个例子中没有定义一个正规化函数。
定义自己的范围类型也允许你指定使用一个不同的子类型 B-树操作符类或者集合, 以便更改排序顺序来决定哪些值会落入到给定的范围中。
如果 subtype 被认为是具有离散值而不是连续值,CREATE TYPE命令应当指定一个canonical函数。正规化函数接收一个输入的范围值,并且必须返回一个可能具有不同界限和格式的等价的范围值。对于两个表示相同值集合的范围(例如[1, 7]和[1, 8)),正规的输出必须一样。选择哪一种表达作为正规的没有关系,只要两个具有不同格式的等价值总是能被映射到具有相同格式的相同值就行。除了调整包含/排除界限格式外,假使期望的补偿比 subtype 能够存储的要大,一个正规化函数可能会舍入边界值。例如,一个timestamp之上的范围类型可能被定义为具有一个一小时的步长, 这样正规化函数可能需要对不是一小时的倍数的界限进行舍入,或者可能直接抛出一个错误。
另外,任何打算要和 GiST 或 SP-GiST 索引一起使用的范围类型应当定一个 subtype 差异或subtype_diff函数(没有subtype_diff时索引仍然能工作,但是可能效率不如提供了差异函数时高)。subtype 差异函数采用两个 subtype 输入值,并且返回表示为一个float8值的差(即X减Y)。在我们上面的例子中,可以使用常规float8减法操作符 之下的函数。但是对于任何其他 subtype,可能需要某种类型转换。还可能需要一些关于如何把差异表达为数字的创新型想法。为了最大的可扩展性,subtype_diff函数应 该同意选中的操作符类和排序规则所蕴含的排序顺序,也就是说,只要它的第一个参数根据排序顺序大于第二个参数,它的结果就应该是正值。
示例
gbase=# CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS 'SELECT EXTRACT(EPOCH FROM (x - y))' LANGUAGE sql STRICT IMMUTABLE;
CREATE FUNCTION
gbase=# CREATE TYPE timerange AS RANGE ( subtype = time,subtype_diff = time_subtype_diff);
CREATE FUNCTION
gbase=# SELECT '[11:10, 23:00]'::timerange;
[11:10:00,23:00:00]
更多关于创建范围类型的信息,参考14.94 CREATE TYPE。
1.1.8 索引
可以为范围类型的表列,创建 GiST 和 SP-GiST 索引。例如,创建GiST索引:
CREATE INDEX reservation_idx ON reservation USING GIST (during);
一个GiST或SP-GiST索引可以加速,涉及以下范围操作符的查询: =、 &&、 <@、@>、 <<、 >>、 -|-、 &<以及 &> (详见5.17 范围函数和操作符)。
此外,B-树和哈希索引可以在范围类型的表列上创建。对于这些索引类型,基本上唯一有用的范围操作就是等值。使用相应的< 和 >操作符,对于范围值定义有一种 B-树排序顺序,但是该顺序相当任意并且在真实世界中通常不怎么有用。范围类型的 B-树和哈希支持主要是为了允许在查询内部进行排序和哈希,而不是创建真正的索引。