Range Types
range types可以用來辨別一些類型(range's subtype)的範圍。例如:ranges of timestamp可以用來表示一個會議室預定的起始和結束時間,對應的類型為tsrange,而timestamp為tsrange的subtype。subtype必須要能夠以某種方式來排序,這樣才能對某個值确定是在range範圍内,還是在之前或之後。
range type十分有效,因為它僅用一個值就可以表示在某個範圍内的一連串值,而且對于overlapping ranges的概念,range type也可以簡潔清楚的表達出來。使用range type的常見場景如:time and date range在會議議程上的安排。
目前内置支援的range類型
目前ADB PG 6.0将PG的代碼基線合入到了9.4,是以也支援了range類型,現在ADB PG 6.0支援的range類型有:
- int4range — Range of integer
- int8range — Range of bigint
- numrange — Range of numeric
- tsrange — Range of timestamp without time zone
- tstzrange — Range of timestamp with time zone
- daterange — Range of date
除此之外,使用者也可以使用create type來建立自定義的range資料類型
使用案例
CREATE TABLE reservation (room int, during tsrange);
INSERT INTO reservation VALUES
(1108, '[2010-01-01 14:30, 2010-01-01 15:30)');
select * from reservation;
room | during
------+-----------------------------------------------
1108 | ["2010-01-01 14:30:00","2010-01-01 15:30:00")
-- Containment
postgres=> SELECT int4range(10, 20) @> 3;
?column?
----------
f
postgres=> SELECT int4range(10, 20) @> 11;
?column?
----------
t
(1 行記錄)
-- Overlaps
postgres=> SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);
?column?
----------
t
-- Extract the upper bound
postgres=> SELECT upper(int8range(15, 25));
upper
-------
25
postgres=> SELECT int4range(10, 20) * int4range(15, 25);
?column?
----------
[15,20)
-- Is the range empty?
postgres=> SELECT isempty(numrange(1, 5));
isempty
---------
f
(1 行記錄)
上下限
range類型的輸入值必須符合以下幾種模式:
(lower-bound,upper-bound)
(lower-bound,upper-bound]
[lower-bound,upper-bound)
[lower-bound,upper-bound]
empty
其中lower-bound可以為subtype的一個值也可以為空,空表示沒有界限。upper-bound類似。
示例:
-- 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;
建構range類型
對于range類型,我們提供了一個建構函數來進行range值的構造,該建構函數相比于上面例子中手寫的方式更加友善。構造函數可以接受2個參數或者3個參數。
當輸入2個參數時,lower-bound是非包含的,upper-bound是包含的,當輸入3個參數時,第三個參數提供了lower-bound和upper-bound是否是包含在range範圍内的。是以第三個參數必須取如下值:"()", "(]", "[)", or "[]"。
-- 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);
range類型操作運算符和函數
range類型支援的運算符如下:
運算符 | 描述 | 示例 | 結果 |
---|---|---|---|
= | equal | int4range(1,5) = '[1,4]'::int4range | t |
<> | not equal | numrange(1.1,2.2) <> numrange(1.1,2.3) | |
< | less than | int4range(1,10) < int4range(2,3) | |
greater than | int4range(1,10) > int4range(1,5) | ||
@> | contains range | int4range(2,4) @> int4range(2,3) | |
contains element | '[2011-01-01,2011-03-01)'::tsrange @> '2011-01-10'::timestamp | ||
<@ | range is contained by | int4range(2,4) <@ int4range(1,7) | |
element is contained by | 42 <@ int4range(1,7) | f | |
&& | overlap (have points in common) | int8range(3,7) && int8range(4,12) | |
<< | strictly left of | int8range(1,10) << int8range(100,110) | |
>> | strictly right of | int8range(50,60) >> int8range(20,30) | |
&< | does not extend to the right of | int8range(1,20) &< int8range(18,20) | |
&> | does not extend to the left of | int8range(7,20) &> int8range(5,10) | |
-|- | is adjacent to | numrange(1.1,2.2) -|- numrange(2.2,3.3) | |
+ | union | numrange(5,15) + numrange(10,20) | [5,20) |
* | intersection | int8range(5,15) * int8range(10,20) | [10,15) |
- | difference | int8range(5,15) - int8range(10,20) | [5,10) |
對于比較操作符<, >, <=, and >= ,首先會比較lower-bound,隻有當lower-bound相等時才會去比較upper-bound。這些比較操作符在range類型的運算中并不常見,支援他們主要是為了支援range類型的B-tree索引通路。
對于left-of/right-of/adjacent這些運算符,如果元素的資料中有empty range存在,那麼直接傳回false。也就是說empty range不在任何range的前面或者後面。
Union和difference運算符在得出的結構如果包含兩個不重疊的子range(也就是說最終結果無法用一個有效的range類型表示),那麼這個運算最後會直接報錯。
下面這個表,展示了range類型提供的函數:
函數 | 傳回類型 | |||
---|---|---|---|---|
lower(anyrange) | range's element type | lower bound of range | lower(numrange(1.1,2.2)) | 1.1 |
upper(anyrange) | upper bound of range | upper(numrange(1.1,2.2)) | 2.2 | |
isempty(anyrange) | boolean | is the range empty? | isempty(numrange(1.1,2.2)) | false |
lower_inc(anyrange) | is the lower bound inclusive? | lower_inc(numrange(1.1,2.2)) | true | |
upper_inc(anyrange) | is the upper bound inclusive? | upper_inc(numrange(1.1,2.2)) | ||
lower_inf(anyrange) | is the lower bound infinite? | lower_inf('(,)'::daterange) | ||
upper_inf(anyrange) | is the upper bound infinite? | upper_inf('(,)'::daterange) |
當range為empty或者對應的界限為無限的時候,lower和upper函數會傳回null。lower_inc, upper_inc, lower_inf,和 upper_inf函數在range為empty的時候都會傳回false。
離散的range類型
在離散的range類型中,每個元素都找到與之“相鄰”的元素,在“相鄰”元素之間沒有其它有效的元素,比如整型和date的range類型。與離散相對應的是連續range類型,連續range類型的特點是對于任意range中的兩個元素,在其之間總能找到其它有效的元素,比如numeric類型。另外,對于離散的range類型,能夠找到某個元素明确的“前”或“後”的元素,是以對于lower-bound或者uppper-bound來說,可以通過使用其“前”或“後”的值來修改是否包含的關系。例如,對于整型的range類型[4,8]可以寫成與之等價的(3,9)。但是,這種改寫對于連續的range類型卻不适用。
對于離散的range類型,需要有一個canonicalization函數,通過這個函數可以得知range中前後元素之間的“步長”。通過這個canonicalization函數可以,可以将離散類型進行如[4,8]到(3,9)的等價轉換。而當沒有指定canonicalization函數函數時,不同格式的range資料将視為不同的range即使實際上他們是等價的。
ADB PG 6.0内置的range類型int4range, int8range, and daterange都指定了canonicalization函數。
自定義新的range類型
使用者可以自己來定義range類型,如:定義一個子類型為float8的range類型:
CREATE TYPE floatrange AS RANGE (
subtype = float8,
subtype_diff = float8mi
);
SELECT '[1.234, 5.678]'::floatrange;
上面float8是一個連續的資料類型,是以其對應的range類型為連續的range類型,是以在上面這個例子中可以不用定義canonicalization函數。
當定義離散range類型時,我們需要定義canonicalization函數,這樣可以對不同形式的range表示而實際上等價的range資料視為等價。另外,canonicalization函數可以用來規整界限值,例如:在timestamp類型上定義了一個range類型的資料,元素之間的步長設定為1個小時,當某個界限值不是小時的整數倍時,canonicalization函數可以用來規整這個界限值,或者抛出錯誤。
在自定義range類型資料時,使用者也可以指定子類型B-tree索引的操作類,比如排序,來自定義哪些元素将會落到一個給定的區間。
另外,當任何range類型需要和GiST或者SP-GiST來配合使用時,需要定義子類型內插補點(subtype_diff)函數(盡管在沒有定義subtype_diff函數時索引仍然是可以工作的,但是當subtype_diff被定義時,索引使用的效率會更高)。子類型的內插補點函數的輸入是兩個子類型值,然後計算傳回兩者的內插補點(如:x減y),這個內插補點類型為float8。在上面float8的自定義range類型中,內插補點函數為兩值相減函數,但是對于其它子類型則需要做一次類型轉換使得最後的結果為float8數值類型。
索引
對于range類型,可以為其建立GiST或SP-GiST索引。如:
CREATE INDEX reservation_idx ON reservation USING gist (during);
GiST或SP-GiST索引可以來加速涉及range算子操作的查詢,如:=, &&, <@, @>, <<, >>, -|-, &<, and &。
除此之外,也可以為range類型建立B-tree索引和hash索引。對于這兩類索引而言,能夠起到加速效果的基本隻有等值查詢,在真實的應用場景中,使用者對range使用等值查詢或者排序查詢是很少的,是以,range類型的B-tree和hash類型的支援,隻是為了支援在查詢中使用排序和hash,而不是起到實際的加速作用。