支援的版本: 目前 (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 中宣告陣列大小或維度數量僅僅是文件記錄;它不會影響執行時行為。

另一種語法,透過使用關鍵字 ARRAY 來符合 SQL 標準,可以用於一維陣列。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),則陣列下標表示式會傳回空值。此外,如果下標超出陣列邊界,也會傳回空值(這種情況不會引發錯誤)。例如,如果 schedule 目前的維度為 [1:3][1:2],那麼引用 schedule[3][3] 會產生 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 或零維的陣列值(否則,沒有現有的下標限制可以替換)。

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

下標指定允許建立不使用基於 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 配置參數設定為 off,以禁止將 NULL 識別為 NULL。

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

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

提示

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

提交更正

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