↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 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 / 7.0 / 6.5 / 6.4
历史版本PostgreSQL 8.1 已于 2010 年 11 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

8.10. 数组 #

PostgreSQL 允许将表列定义为变长多维数组。可以创建任何内置或用户定义的基础类型、枚举类型或复合类型的数组。尚不支持域的数组。

8.10.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 标准的语法可用于一维数组。pay_by_quarter 也可以定义为:

    pay_by_quarter  integer ARRAY[4],

这种语法要求用一个整数常量来表示数组大小。不过,和前面一样,PostgreSQL 并不强制执行大小限制。

8.10.2. 数组值输入

要把数组值写成字面常量,请将元素值放在花括号内,并用逗号分隔。(如果你了解 C,这与 C 中初始化结构体的语法有些类似。)你可以给任意元素值加上双引号;如果它包含逗号或花括号,则必须这样做。(更多细节见下文。)因此,数组常量的一般格式如下:

'{ val1 delim val2 delim ... }'

其中 delim 是该类型的分隔符字符,它记录在其 pg_type 条目中。在 PostgreSQL 发行版提供的标准数据类型里,除类型 box 使用分号(;)外,其余都使用逗号(,)。每个 val 要么是数组元素类型的常量,要么是一个子数组。数组常量的一个示例是:

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

这个常量是一个 3 x 3 的二维数组,由三个整数子数组构成。

(这类数组常量实际上只是第 4.1.2.5 节中讨论的通用类型常量的一种特例。该常量最初会被当作字符串处理,然后传递给数组输入转换例程。可能需要显式指定类型。)

现在我们来看几个 INSERT 语句。

INSERT INTO sal_emp
    VALUES ('Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"meeting"}}');
ERROR:  multidimensional arrays must have array expressions with matching dimensions

注意,多维数组的各个子数组在每个维度上的长度必须匹配。不匹配会导致一条错误报告。

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)

当前数组实现的一个局限是,数组中的单个元素不能是 SQL 空值。整个数组可以被设置为 null,但不能让数组的一部分元素为 null 而另一部分不为 null。(将来这可能会改变。)

也可以使用 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.10 节。

8.10.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)

我们也可以写成

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

得到相同的结果。只要任何一个下标写成 lower:upper 的形式,数组下标操作就总是被当作数组切片来处理。对于只指定了一个数值的 下标,会假定其下界为 1,例如:

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

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

从数组当前边界之外取值会得到一个 SQL 空值,而不是错误。例如,如果 schedule 当前的维度是 [1:3][1:2],那么引用 schedule[3][3] 会得到 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_upper 和 array_lower 取回维度信息,它们分别返回指定数组维度的上界和下界:

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

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

8.10.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';

已存储的数组值可以通过给与已有元素相邻的元素赋值,或者给与已有数据 相邻或重叠的切片赋值来扩展。例如,如果数组 myarray 当前有 4 个元素,那么在一次更新把值赋给 myarray[5] 之后,它将有 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_prepend、array_append 或 array_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}}

8.10.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.17 节。上面的查询可以改写为:

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

此外,如果要查找数组中所有值都等于 10000 的行,可以使用:

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

提示

数组不是集合;搜索特定数组元素可能是数据库设计不当的信号。可以考虑使用一个独立的表,让原本会成为数组元素的每个项各占一行。这样会更容易搜索,而且在元素数量很多时通常也有更好的可伸缩性。

8.10.6. 数组输入和输出语法

数组值的外部文本表示由若干项构成,这些项会按照数组元素类型的 I/O 转换规则进行解释,再加上一些表示数组结构的修饰。修饰部分包括数组值外层的花括号({ 和 }),以及相邻项之间的分隔符字符。分隔符字符通常是逗号(,),但也可能是别的字符:它由数组元素类型的 typdelim 设置决定。在 PostgreSQL 发行版提供的标准数据类型中,除类型 box 使用分号(;)外,其余都使用逗号。在多维数组中,每个维度(行、平面、立方体等)都有自己的一层花括号,并且同一级别相邻的、由花括号括起的各项之间必须写出分隔符。

如果元素值是空字符串、包含花括号、分隔符字符、双引号、反斜线或空白,数组输出例程就会在元素值外面加上双引号。元素值中嵌入的双引号和反斜线会用反斜线转义。对于数字数据类型,可以安全地假定不会出现双引号;但对于文本数据类型,则应准备好处理带引号和不带引号两种情况。(这是相对 7.2 之前 PostgreSQL 版本的一个行为变化。)

默认情况下,数组各维度的下界下标值都设为 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 时,数组输出例程才会在结果中包含显式维度信息。

这种语法也可以用来在数组字面值中指定非默认的数组下标。例如:

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)

如前所述,在写数组值时,可以给任意单个数组元素加上双引号。如果 元素值不加引号就会让数组值解析器产生混淆,那么你必须 这样做。例如,包含花括号、逗号(或者该数据类型的分隔符字符)、 双引号、反斜线,或者前后带有空白的元素值,必须使用双引号。要在一个带 双引号的数组元素值中放入双引号或反斜线,需要在它前面加反斜线。 或者,你也可以用反斜线转义来保护所有原本会被当作数组语法的数据字符。

你可以在左花括号之前或右花括号之后添加空白。也可以在任意单个项字符串之前或之后添加空白。在所有这些情况下,这些空白都会被忽略。不过,双引号元素内部的空白,或者元素中夹在两个非空白字符之间的空白,不会被忽略。

注意

请记住,你在 SQL 命令中写下的内容会先被解释为字符串字面值, 然后才被解释为数组。这会使你所需的反斜线数量加倍。例如,要插入 一个包含一个反斜线和一个双引号的 text 数组值,你需要写成:

INSERT ... VALUES ('{"\\\\","\\""}');

转义字符串处理器会去掉一层反斜线,因此到达数组值解析器的内容 形如 {"\\","\""}。而传给 text 数据类型输入 例程的字符串则分别变成 \ 和 "。(如果我们 使用的是输入例程同样对反斜线做特殊处理的数据类型,例如 bytea,那么要在命令中写入八个反斜线才能让存储的数组元素 中出现一个反斜线。)可以使用美元引用(见 第 4.1.2.2 节)来避免双写反斜线。

提示

在 SQL 命令中写数组值时,ARRAY 构造器语法(见第 4.2.10 节)通常比数组字面量语法更容易使用。在 ARRAY 中,各个元素值的写法与它们不作为数组成员时完全相同。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。