支援的版本: 目前 (17) / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

8.15. 陣列 #

PostgreSQL 允許將表格的欄位定義為可變長度的多維陣列。可以建立任何內建或使用者定義的基礎類型、列舉類型、複合類型、範圍類型或網域的陣列。

8.15.1. 陣列類型的宣告 #

為了說明陣列類型的使用,我們建立此表格

CREATE TABLE sal_emp (
    name            text,
    pay_by_quarter  integer[],
    schedule        text[][]
);

如所示,陣列資料類型是透過將方括號 ([]) 附加到陣列元素的資料類型名稱來命名的。上面的命令將建立一個名為 sal_emp 的表格,其中包含一個 text 類型的欄位 (name)、一個 integer 類型的一維陣列 (pay_by_quarter),它代表員工每季的薪水,以及一個 text 類型的二維陣列 (schedule),它代表員工的每週行程。

CREATE TABLE 的語法允許指定陣列的確切大小,例如

CREATE TABLE tictactoe (
    squares   integer[3][3]
);

但是,目前的實作會忽略任何提供的陣列大小限制,也就是說,其行為與未指定長度的陣列相同。

目前的實作也不會強制執行宣告的維度數。特定元素類型的陣列都被視為相同的類型,無論大小或維度數為何。因此,在 CREATE TABLE 中宣告陣列大小或維度數僅僅是文件,它不會影響執行時的行為。

另一種語法,符合 SQL 標準,使用關鍵字 ARRAY,可用於一維陣列。pay_by_quarter 可以定義為

    pay_by_quarter  integer ARRAY[4],

或者,如果沒有指定陣列大小

    pay_by_quarter  integer ARRAY,

但是,與之前一樣,PostgreSQL 在任何情況下都不會強制執行大小限制。

8.15.2. 陣列值的輸入 #

要將陣列值寫為文字常數,請將元素值括在大括號內,並用逗號分隔。(如果您了解 C,這與 C 語法初始化結構體非常相似。)您可以將雙引號括在任何元素值周圍,如果它包含逗號或大括號,則必須這樣做。(更多詳細資料如下。)因此,陣列常數的一般格式如下

'{ val1 delim val2 delim ... }'

其中 delim 是該類型的分隔符號字元,如其 pg_type 條目中所記錄。在 PostgreSQL 發行版本中提供的標準資料類型中,除了 box 類型使用分號 (;) 之外,所有類型都使用逗號 (,)。每個 val 都是陣列元素類型的常數或子陣列。陣列常數的一個例子是

'{{1,2,3},{4,5,6},{7,8,9}}'

這個常數是一個二維、3x3 的陣列,由三個整數子陣列組成。

要將陣列常數的元素設定為 NULL,請為元素值寫入 NULL。(NULL 的任何大小寫變體都可以。)如果您想要一個實際的字串值 NULL,您必須用雙引號將其括起來。

(這些陣列常數實際上只是 第 4.1.2.7 節 中討論的通用類型常數的一種特殊情況。該常數最初被視為一個字串,並傳遞給陣列輸入轉換常式。可能需要明確的類型指定。)

現在我們可以展示一些 INSERT 陳述式

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"training", "presentation"}}');

INSERT INTO sal_emp
    VALUES ('Carol',
    '{20000, 25000, 25000, 25000}',
    '{{"breakfast", "consulting"}, {"meeting", "lunch"}}');

前兩個插入的結果如下所示

SELECT * FROM sal_emp;
 name  |      pay_by_quarter       |                 schedule
-------+---------------------------+-------------------------------------------
 Bill  | {10000,10000,10000,10000} | {{meeting,lunch},{training,presentation}}
 Carol | {20000,25000,25000,25000} | {{breakfast,consulting},{meeting,lunch}}
(2 rows)

多維陣列必須為每個維度具有相符的範圍。不匹配會導致錯誤,例如

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"meeting"}}');
ERROR:  malformed array literal: "{{"meeting", "lunch"}, {"meeting"}}"
DETAIL:  Multidimensional arrays must have sub-arrays with matching dimensions.

也可以使用 ARRAY 建構子語法

INSERT INTO sal_emp
    VALUES ('Bill',
    ARRAY[10000, 10000, 10000, 10000],
    ARRAY[['meeting', 'lunch'], ['training', 'presentation']]);

INSERT INTO sal_emp
    VALUES ('Carol',
    ARRAY[20000, 25000, 25000, 25000],
    ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']]);

請注意,陣列元素是普通的 SQL 常數或表達式;例如,字串文字是用單引號引用的,而不是像在陣列文字中那樣用雙引號引用的。ARRAY 建構子語法在 第 4.2.12 節 中有更詳細的討論。

8.15.3. 存取陣列 #

現在,我們可以在表格上執行一些查詢。首先,我們展示如何存取陣列的單個元素。此查詢檢索第二季度薪水變動的員工姓名

SELECT name FROM sal_emp WHERE pay_by_quarter[1] <> pay_by_quarter[2];

 name
-------
 Carol
(1 row)

陣列下標數字寫在方括號內。預設情況下,PostgreSQL 對陣列使用基於 1 的編號約定,也就是說,一個具有 n 個元素的陣列從 array[1] 開始,到 array[n] 結束。

此查詢檢索所有員工的第三季薪水

SELECT pay_by_quarter[3] FROM sal_emp;

 pay_by_quarter
----------------
          10000
          25000
(2 rows)

我們也可以存取陣列中任意的矩形切片或子陣列。陣列切片用寫作 lower-bound:upper-bound 的方式表示一個或多個陣列維度。例如,這個查詢會擷取 Bill 在一週的前兩天行程中的第一個項目

SELECT schedule[1:2][1:1] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{meeting},{training}}
(1 row)

如果任何維度寫成切片形式,也就是包含冒號,那麼所有維度都會被視為切片。任何只有單個數字(沒有冒號)的維度都會被視為從 1 到指定的數字。例如,[2] 會被視為 [1:2],如下面的範例所示

SELECT schedule[1:2][2] FROM sal_emp WHERE name = 'Bill';

                 schedule
-------------------------------------------
 {{meeting,lunch},{training,presentation}}
(1 row)

為了避免與非切片的情況混淆,最好對所有維度都使用切片語法,例如 [1:2][1:1],而不是 [2][1:1]

可以省略切片指定詞的 lower-bound 和/或 upper-bound;遺漏的邊界會被陣列的下標的下限或上限取代。例如

SELECT schedule[:2][2:] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{lunch},{presentation}}
(1 row)

SELECT schedule[:][1:1] FROM sal_emp WHERE name = 'Bill';

        schedule
------------------------
 {{meeting},{training}}
(1 row)

如果陣列本身或任何下標運算式為 null,則陣列下標運算式將傳回 null。此外,如果下標超出陣列範圍,也會傳回 null(這種情況不會引發錯誤)。例如,如果 schedule 目前的維度為 [1:3][1:2],則引用 schedule[3][3] 會產生 NULL。同樣地,帶有錯誤下標數量的陣列引用會產生 null 而不是錯誤。

如果陣列本身或任何下標運算式為 null,陣列切片運算式也會產生 null。然而,在其他情況下,例如選取完全超出目前陣列範圍的陣列切片,切片運算式會產生一個空的(零維)陣列而不是 null。(這與非切片的行為不符,是為了歷史原因而設計的。)如果請求的切片部分重疊陣列範圍,則它會默默地縮減到僅重疊的區域,而不是傳回 null。

可以使用 array_dims 函數來檢索任何陣列值的目前維度

SELECT array_dims(schedule) FROM sal_emp WHERE name = 'Carol';

 array_dims
------------
 [1:2][1:2]
(1 row)

array_dims 會產生 text 結果,這對於人們閱讀很方便,但對於程式可能不方便。也可以使用 array_upperarray_lower 來檢索維度,它們分別傳回指定陣列維度的上限和下限

SELECT array_upper(schedule, 1) FROM sal_emp WHERE name = 'Carol';

 array_upper
-------------
           2
(1 row)

array_length 將傳回指定陣列維度的長度

SELECT array_length(schedule, 1) FROM sal_emp WHERE name = 'Carol';

 array_length
--------------
            2
(1 row)

cardinality 傳回陣列中所有維度的元素總數。它實際上是呼叫 unnest 將產生的行數

SELECT cardinality(schedule) FROM sal_emp WHERE name = 'Carol';

 cardinality
-------------
           4
(1 row)

8.15.4. 修改陣列 #

陣列值可以完全被取代

UPDATE sal_emp SET pay_by_quarter = '{25000,25000,27000,27000}'
    WHERE name = 'Carol';

或使用 ARRAY 運算式語法

UPDATE sal_emp SET pay_by_quarter = ARRAY[25000,25000,27000,27000]
    WHERE name = 'Carol';

也可以在單個元素上更新陣列

UPDATE sal_emp SET pay_by_quarter[4] = 15000
    WHERE name = 'Bill';

或在切片中更新

UPDATE sal_emp SET pay_by_quarter[1:2] = '{27000,27000}'
    WHERE name = 'Carol';

也可以使用省略 lower-bound 和/或 upper-bound 的切片語法,但僅當更新不是 NULL 或零維的陣列值時(否則,沒有現有的下標限制可以取代)。

透過分配給尚未存在的元素,可以擴大儲存的陣列值。先前存在的那些位置和新分配的元素之間的任何位置都將填入 null。例如,如果陣列 myarray 目前有 4 個元素,則在分配給 myarray[6] 之後,它將有六個元素;myarray[5] 將包含 null。目前,僅允許以這種方式擴大一維陣列,而不允許擴大多維陣列。

下標分配允許建立不使用基於 1 的下標的陣列。例如,可以分配給 myarray[-2:7] 以建立一個下標值從 -2 到 7 的陣列。

也可以使用串聯運算子 || 建構新的陣列值

SELECT ARRAY[1,2] || ARRAY[3,4];
 ?column?
-----------
 {1,2,3,4}
(1 row)

SELECT ARRAY[5,6] || ARRAY[[1,2],[3,4]];
      ?column?
---------------------
 {{5,6},{1,2},{3,4}}
(1 row)

串聯運算子允許將單個元素推送到一維陣列的開頭或結尾。它也接受兩個 N 維陣列,或一個 N 維陣列和一個 N+1 維陣列。

當將單個元素推送到一維陣列的開頭或結尾時,結果是一個具有與陣列運算元相同的下限下標的陣列。例如

SELECT array_dims(1 || '[0:1]={2,3}'::int[]);
 array_dims
------------
 [0:2]
(1 row)

SELECT array_dims(ARRAY[1,2] || 3);
 array_dims
------------
 [1:3]
(1 row)

當串聯具有相同維度數量的兩個陣列時,結果會保留左側運算元的外部維度的下限下標。結果是一個包含左側運算元的每個元素,然後是右側運算元的每個元素的陣列。例如

SELECT array_dims(ARRAY[1,2] || ARRAY[3,4,5]);
 array_dims
------------
 [1:5]
(1 row)

SELECT array_dims(ARRAY[[1,2],[3,4]] || ARRAY[[5,6],[7,8],[9,0]]);
 array_dims
------------
 [1:5][1:2]
(1 row)

當將 N 維陣列推送到 N+1 維陣列的開頭或結尾時,結果與上面的元素陣列情況類似。每個 N 維子陣列本質上是 N+1 維陣列外部維度的元素。例如

SELECT array_dims(ARRAY[1,2] || ARRAY[[3,4],[5,6]]);
 array_dims
------------
 [1:3][1:2]
(1 row)

也可以使用函數 array_prependarray_appendarray_cat 建構陣列。前兩個函數僅支援一維陣列,但 array_cat 支援多維陣列。一些範例

SELECT array_prepend(1, ARRAY[2,3]);
 array_prepend
---------------
 {1,2,3}
(1 row)

SELECT array_append(ARRAY[1,2], 3);
 array_append
--------------
 {1,2,3}
(1 row)

SELECT array_cat(ARRAY[1,2], ARRAY[3,4]);
 array_cat
-----------
 {1,2,3,4}
(1 row)

SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[5,6]);
      array_cat
---------------------
 {{1,2},{3,4},{5,6}}
(1 row)

SELECT array_cat(ARRAY[5,6], ARRAY[[1,2],[3,4]]);
      array_cat
---------------------
 {{5,6},{1,2},{3,4}}

在簡單的情況下,建議使用上面討論的串聯運算子,而不是直接使用這些函數。但是,由於串聯運算子被多載以服務所有三種情況,因此在某些情況下,使用其中一個函數有助於避免歧義。例如,考慮一下

SELECT ARRAY[1, 2] || '{3, 4}';  -- the untyped literal is taken as an array
 ?column?
-----------
 {1,2,3,4}

SELECT ARRAY[1, 2] || '7';                 -- so is this one
ERROR:  malformed array literal: "7"

SELECT ARRAY[1, 2] || NULL;                -- so is an undecorated NULL
 ?column?
----------
 {1,2}
(1 row)

SELECT array_append(ARRAY[1, 2], NULL);    -- this might have been meant
 array_append
--------------
 {1,2,NULL}

在上面的範例中,剖析器在串聯運算子的一側看到一個整數陣列,而在另一側看到一個未確定類型的常數。它用來解析常數類型的啟發式方法是假設它與運算子的另一個輸入類型相同,在這種情況下,是整數陣列。因此,串聯運算子被假定為代表 array_cat,而不是 array_append。當這是錯誤的選擇時,可以透過將常數轉換為陣列的元素類型來修正它;但是,明確使用 array_append 可能是一個更好的解決方案。

8.15.5. 在陣列中搜尋 #

要在陣列中搜尋值,必須檢查每個值。如果知道陣列的大小,則可以手動執行此操作。例如

SELECT * FROM sal_emp WHERE pay_by_quarter[1] = 10000 OR
                            pay_by_quarter[2] = 10000 OR
                            pay_by_quarter[3] = 10000 OR
                            pay_by_quarter[4] = 10000;

但是,對於大型陣列,這很快就會變得繁瑣,並且如果陣列的大小未知,則沒有幫助。第 9.25 節中描述了一種替代方法。上面的查詢可以用以下內容替換

SELECT * FROM sal_emp WHERE 10000 = ANY (pay_by_quarter);

此外,您可以使用以下方法找到陣列中所有值都等於 10000 的列

SELECT * FROM sal_emp WHERE 10000 = ALL (pay_by_quarter);

或者,可以使用 generate_subscripts 函數。例如

SELECT * FROM
   (SELECT pay_by_quarter,
           generate_subscripts(pay_by_quarter, 1) AS s
      FROM sal_emp) AS foo
 WHERE pay_by_quarter[s] = 10000;

此函數在表 9.68 中描述。

您也可以使用 && 運算子搜尋陣列,該運算子檢查左側運算元是否與右側運算元重疊。例如

SELECT * FROM sal_emp WHERE pay_by_quarter && ARRAY[10000];

這個和其他陣列運算子在第 9.19 節 中進一步描述。可以使用適當的索引來加速它,如第 11.2 節 中所述。

您也可以使用 array_positionarray_positions 函數搜尋陣列中的特定值。前者傳回陣列中值第一次出現的下標;後者傳回一個陣列,其中包含陣列中該值的所有出現的下標。例如

SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon');
 array_position
----------------
              2
(1 row)

SELECT array_positions(ARRAY[1, 4, 3, 1, 3, 4, 2, 1], 1);
 array_positions
-----------------
 {1,4,8}
(1 row)

提示

陣列並非集合;搜尋特定的陣列元素可能代表資料庫設計錯誤。建議使用一個獨立的表格,每一列代表一個陣列元素。這樣更容易搜尋,而且在大量元素的情況下更可能具有更好的擴展性。

8.15.6. 陣列輸入與輸出語法 #

陣列值的外部文字表示形式由元素組成,這些元素根據陣列元素類型的 I/O 轉換規則進行解析,加上指示陣列結構的裝飾。 裝飾由大括號({})包圍陣列值,加上相鄰元素之間的分隔符。 分隔符通常是逗號(,),但也可以是其他字元:它由陣列元素類型的 typdelim 設定決定。 在 PostgreSQL 發行版中提供的標準資料類型中,除了 box 類型使用分號(;)之外,所有類型都使用逗號。 在多維陣列中,每個維度(行、平面、立方體等)都有自己層級的大括號,並且必須在同一層級的相鄰大括號實體之間編寫分隔符。

如果元素值為空字串、包含大括號、分隔符、雙引號、反斜線或空白字元,或與單詞 NULL 匹配,則陣列輸出例程會在元素值周圍加上雙引號。 嵌入在元素值中的雙引號和反斜線將會進行反斜線跳脫。 對於數值資料類型,可以安全地假設永遠不會出現雙引號,但對於文字資料類型,應準備好應對引號的存在或不存在。

預設情況下,陣列維度的下限索引值設定為 1。 要表示具有其他下限的陣列,可以在寫入陣列內容之前顯式指定陣列下標範圍。 此裝飾由每個陣列維度的下限和上限周圍的方括號([])組成,中間用冒號(:)分隔。 陣列維度裝飾後跟一個等號(=)。 例如:

SELECT f1[1][-2][3] AS e1, f1[1][-1][5] AS e2
 FROM (SELECT '[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[] AS f1) AS ss;

 e1 | e2
----+----
  1 |  6
(1 row)

僅當有一個或多個下限不同於 1 時,陣列輸出例程才會在其結果中包含顯式維度。

如果為元素寫入的值為 NULL(任何大小寫變體),則該元素被視為 NULL。 任何引號或反斜線的存在都會禁用此功能,並允許輸入文字字串值 NULL。 此外,為了與 PostgreSQL 的 8.2 之前的版本向後相容,可以關閉 array_nulls 配置參數,以抑制將 NULL 識別為 NULL。

如先前所示,在寫入陣列值時,您可以使用雙引號括住任何單個陣列元素。 如果元素值會混淆陣列值解析器,則必須這樣做。 例如,包含大括號、逗號(或資料類型的分隔符)、雙引號、反斜線或前導或尾隨空白字元的元素必須用雙引號引起來。 空字串和與單詞 NULL 匹配的字串也必須用引號引起來。 要在引用的陣列元素值中放置雙引號或反斜線,請在其前面加上反斜線。 或者,您可以避免使用引號,並使用反斜線跳脫來保護所有其他方式會被視為陣列語法的資料字元。

您可以在左大括號之前或右大括號之後添加空白。 您也可以在任何個別項目字串之前或之後添加空白。 在所有這些情況下,空白將被忽略。 但是,雙引號元素內的空白,或兩側都由元素的非空白字元包圍的空白,不會被忽略。

提示

ARRAY 構造函數語法(請參閱 第 4.2.12 節)在 SQL 命令中編寫陣列值時,通常比陣列文字語法更容易使用。 在 ARRAY 中,單個元素值的編寫方式與它們不是陣列成員時的編寫方式相同。

提交更正

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