支持的版本: 当前 (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)

我们还可以访问数组的任意矩形切片或子数组。数组切片通过为一维或多维数组写入 下限:上限 来表示。例如,此查询检索 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]

可以省略切片说明符的 下限 和/或 上限;缺少的界限将替换为数组下标的下限或上限。例如

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(这种情况不会引发错误)。例如,如果 schedule 当前的维度为 [1:3][1:2],则引用 schedule[3][3] 会产生 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 配置参数设置为 off 以禁止将 NULL 识别为 NULL。

如前所示,在编写数组值时,可以在任何单个数组元素周围使用双引号。如果元素值会使数组值解析器感到困惑,则必须这样做。例如,包含花括号、逗号(或数据类型分隔符字符)、双引号、反斜杠或前导或尾随空格的元素必须用双引号引起来。空字符串和与单词 NULL 匹配的字符串也必须用引号引起来。要将双引号或反斜杠放入带引号的数组元素值中,请在其前面加上反斜杠。或者,您可以避免使用引号,并使用反斜杠转义来保护所有可能被视为数组语法的数据字符。

您可以在左花括号之前或右花括号之后添加空格。您也可以在任何单个项目字符串之前或之后添加空格。在所有这些情况下,空格都将被忽略。但是,双引号元素中的空格,或被元素的非空格字符包围的空格,不会被忽略。

提示

当在 SQL 命令中编写数组值时,ARRAY 构造函数语法(请参阅 第 4.2.12 节)通常比数组文字语法更容易使用。在 ARRAY 中,单个元素值的写入方式与它们不是数组的成员时的写入方式相同。

提交更正

如果您在文档中发现任何不正确、与特定功能不符或需要进一步澄清的地方,请使用此表单报告文档问题。