PostgreSQL 允许将表的列定义为可变长度多维数组。可以创建任何内建或用户定义基本类型、枚举类型、复合类型、范围类型或域的数组。
为了说明数组类型的用法,我们创建此表
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 在任何情况下都不强制执行大小限制。
要将数组值写为文字常量,请用大括号括住元素值并用逗号分隔。 (如果你了解 C 语言,这与用 C 语言初始化结构的语法非常相似。)你可以在任何元素值周围加上双引号,如果元素值中包含逗号或大括号,则必须加上双引号。 (更多详细信息见下文。)因此,数组常量的一般格式如下
'{val1
delim
val2
delim
... }'
其中 delim
是该类型的分隔符字符,如其在 pg_type
条目中所记录。 在 PostgreSQL 发行版中提供的标准数据类型中,除了类型 box
之外,所有类型都使用逗号 (,
),而类型 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。
现在,我们可以针对该表运行一些查询。首先,我们演示如何访问数组的单个元素。该查询检索在第二季度奖金发生变化的员工姓名
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 在一周中的前两天内的日程中的第一项下限
:上限
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。此外,如果下标超出数组边界,则会返回 null(此情况不会引发错误)。例如,如果 schedule
当前具有维度 [1:3][1:2]
,那么引用 schedule[3][3]
将产生 NULL。同样,具有错误下标数量的数组引用会产生 null 而不是错误。
如果数组本身或任何下标表达式为 null,数组切片表达式也同样产生 null。然而,在其他情况下,例如选择完全超出当前数组边界的数组切片,切片表达式会产生一个空数组(0 维),而不是 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 或 0 维的数组值时(否则,没有现有的下标限制可替代)。
可以通过对尚未出现的元素进行分配来扩大存储的数组值。之前存在的元素和新分配的元素之间的任何位置都将被填充为 null。例如,如果数组 myarray
当前有 4 个元素,那么对 myarray[6]
进行分配后,它将有 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_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.68 中 进行说明。
你还可以使用 &&
运算符搜索数组,该运算符检查左操作数是否与右操作数重叠。例如:
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
匹配,则将在元素值周围加上双引号。元素值中嵌入的双引号和反斜杠将使用反斜杠进行转义。对于数字数据类型,可以安全地假设永远不会出现双引号,但对于文本数据类型,应该准备好应对有引号或无引号的情况。
默认情况下,数组纬度的下限索引值设置为 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
中,各个元素值以不属于数组时的相同方式写入。