2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2

8.17. 範圍型別 #

範圍型別是表示某種元素型別(稱為範圍的子型別)的值的範圍的資料型別。例如,timestamp 的範圍可能用於表示會議室預訂的時間範圍。在這種情況下,資料型別是 tsrange(“timestamp range”的縮寫),而 timestamp 是子型別。子型別必須具有全序關係,以便明確元素值是否在某個值範圍內、在此範圍之前還是在此範圍之後。

範圍型別很有用,因為它們用單個範圍值表示許多元素值,並且像“重疊範圍”這樣的概念可以清晰地表達。將時間和日期範圍用於排程目的就是最清晰的例子;但價格範圍、儀器的測量範圍等也可能很有用。

每個範圍型別都有一個對應的多範圍型別。多範圍是連續的、非空、非 NULL 範圍的有序列表。大多數範圍運算子也適用於多範圍,並且它們有自己的一些函式。

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.58表 9.60 以獲取範圍型別的運算子和函式的完整列表。

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) 表示相同的數值集合;但對於 numeric 範圍則不是這樣。

離散範圍型別應具有一個規範化函式,該函式應瞭解元素型別的所需步長。規範化函式負責將範圍型別的等效值轉換為具有相同表示形式,特別是始終包含或排除邊界。如果未指定規範化函式,則格式不同的範圍將始終被視為不等,即使它們在現實中可能表示相同的值集。

內建範圍型別 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.58

此外,還可以為範圍型別的表列建立 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

提交更正

如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用 此表單 來報告文件問題。