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 / 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 型別的 1 維陣列 (pay_by_quarter),該陣列表示員工按季度的薪資,以及一個 text 型別的 2 維陣列 (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"}}');

前面的兩個 INSERT 語句的結果如下

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 使用基於一的編號約定進行陣列編號,即,一個包含 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。目前,這種方式的擴充套件只允許用於一維陣列,而不允許用於多維陣列。

下標賦值允許建立不使用基於一的下標的陣列。例如,可以分配給 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.70 中進行了描述。

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

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,則陣列輸出例程會將元素值括在雙引號中。嵌入元素值中的雙引號和反斜槓將被反斜槓轉義。對於數值資料型別,可以安全地假設雙引號永遠不會出現,但對於文字資料型別,應準備好應對引號的出現或缺席。

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

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)

當陣列的下界不等於一時,陣列輸出例程才會在其結果中包含顯式維度。

如果為元素寫的值是 NULL(任何大小寫變體),則該元素被視為 NULL。任何引號或反斜槓的存在都會停用此功能,並允許輸入字面字串值“NULL”。此外,為了與 PostgreSQL 8.2 之前的版本向後相容,可以將 array_nulls 配置引數設定為 off 以禁止將 NULL 識別為 NULL。

如前所述,在寫入陣列值時,您可以用雙引號括起任何單個數組元素。如果元素值否則會混淆陣列值解析器,您必須這樣做。例如,包含花括號、逗號(或資料型別分隔符字元)、雙引號、反斜槓或前導/尾隨空格的元素必須用雙引號括起來。空字串和匹配單詞 NULL 的字串也必須括起來。要將雙引號或反斜槓放入帶引號的陣列元素值中,請在其前面加上反斜槓。或者,您可以避免使用引號,而是使用反斜槓轉義來保護所有將具有陣列語法含義的資料字元。

您可以在左花括號前或右花括號後新增空格。您也可以在任何單個專案字串之前或之後新增空格。在所有這些情況下,空格都會被忽略。但是,雙引號內元素中的空格,或者元素中被非空格字元兩邊包圍的空格,則不會被忽略。

提示

在 SQL 命令中寫入陣列值時,ARRAY 建構函式語法(參見 第 4.2.12 節)通常比陣列文字語法更容易使用。在 ARRAY 中,單個元素值將按照它們不是陣列成員時的方式編寫。

提交更正

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