PostgreSQL 允许将表列定义为可变长度的多维数组。可以创建任何内置或用户定义的基类型、枚举类型、复合类型、范围类型或域的数组。
为了说明数组类型的用法,我们创建此表
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 在任何情况下都不会强制执行大小限制。
要将数组值作为字面量常量写入,请将元素值括在花括号内并用逗号分隔。(如果您熟悉 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 节 中有更详细的讨论。
现在,我们可以对表运行一些查询。首先,我们展示如何访问数组中的单个元素。此查询检索在第二季度薪资发生变化的员工姓名
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)
我们还可以访问数组的任意矩形切片或子数组。数组切片通过为一维或多维数组编写 来表示。例如,此查询检索 Bill 在一周前两天的日程安排的第一项lower-bound:upper-bound
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_upper 和 array_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)
数组值可以完全替换
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_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}}
在简单情况下,上面讨论的连接运算符优于直接使用这些函数。但是,由于连接运算符被重载以服务于所有三种情况,因此在某些情况下使用函数有助于避免歧义。例如,考虑
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 可能是更好的解决方案。
要搜索数组中的值,必须检查每个值。如果知道数组的大小,可以手动完成。例如
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_position 和 array_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)
数组不是集合;搜索特定的数组元素可能是数据库设计不当的迹象。考虑使用一个单独的表,其中每个条目对应一个数组元素。这样更容易搜索,并且对于大量元素来说,性能可能更好。
数组值的外部文本表示由根据数组元素类型的 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 中,单个元素值将按照它们不是数组成员时的方式编写。