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

4.2. 值表达式 #

值表达式用于多种上下文,例如 SELECT 命令的目标列表、INSERTUPDATE 中的新列值,或多个命令中的搜索条件。值表达式的结果有时称为 标量,以区别于表表达式(即表)的结果。因此,值表达式也称为 标量表达式(甚至简称为 表达式)。表达式语法允许使用算术、逻辑、集合和其他操作从基本部分计算值。

值表达式是以下之一:

  • 常量或字面值

  • 列引用

  • 在函数定义或准备语句的主体中,位置参数引用

  • 带下标的表达式

  • 字段选择表达式

  • 运算符调用

  • 函数调用

  • 聚合表达式

  • 窗口函数调用

  • 类型转换

  • 排序规则表达式

  • 标量子查询

  • 数组构造器

  • 行构造器

  • 括号中的另一个值表达式(用于分组子表达式和覆盖优先级

除了这个列表之外,还有一些构造可以归类为表达式,但不遵循任何通用语法规则。这些通常具有函数或运算符的语义,并在第 9 章的适当位置进行解释。例如是 IS NULL 子句。

我们已经在第 4.1.2 节中讨论了常量。以下各节将讨论剩余的选项。

4.2.1. 列引用 #

列可以以下形式引用:

correlation.columnname

相关名 是表的名称(可能带有模式名限定),或者是通过 FROM 子句定义的表的别名。当列名在当前查询使用的所有表中唯一时,可以省略相关名和分隔点。(另请参见第 7 章。)

4.2.2. 位置参数 #

位置参数引用用于指示一个值,该值是从外部提供给 SQL 语句的。参数在 SQL 函数定义和准备查询中使用。某些客户端库还支持将数据值与 SQL 命令字符串分开指定,在这种情况下,参数用于引用与命令字符串分离的数据值。参数引用的形式是:

$number

例如,考虑一个名为 dept 的函数的定义,如下所示:

CREATE FUNCTION dept(text) RETURNS dept
    AS $$ SELECT * FROM dept WHERE name = $1 $$
    LANGUAGE SQL;

这里 $1 指的是函数被调用时第一个函数参数的值。

4.2.3. 下标 #

如果一个表达式产生一个数组类型的值,那么可以通过写入以下内容来提取数组值的特定元素:

expression[subscript]

或者提取多个相邻元素(一个“数组切片”)可以通过写入以下内容:

expression[lower_subscript:upper_subscript]

(此处,方括号 [ ] 表示字面量。)每个 下标 本身是一个表达式,它将被四舍五入为最接近的整数值。

通常,数组 表达式 必须用括号括起来,但当要下标的表达式只是一个列引用或位置参数时,可以省略括号。此外,当原始数组是多维的时,可以连接多个下标。例如:

mytable.arraycolumn[4]
mytable.two_d_column[17][34]
$1[10:42]
(arrayfunction(a,b))[42]

最后一个示例中的括号是必需的。有关数组的更多信息,请参阅第 8.15 节

4.2.4. 字段选择 #

如果一个表达式产生一个复合类型(行类型)的值,那么可以通过写入以下内容来提取行的特定字段:

expression.fieldname

通常,行 表达式 必须用括号括起来,但当要从中选择的表达式只是一个表引用或位置参数时,可以省略括号。例如:

mytable.mycolumn
$1.somecolumn
(rowfunction(a,b)).col3

(因此,限定列引用实际上只是字段选择语法的特例。)提取复合类型表列字段的一个重要特例是:

(compositecol).somefield
(mytable.compositecol).somefield

此处括号是必需的,以表明 compositecol 是一个列名而不是表名,或者在第二种情况下 mytable 是一个表名而不是模式名。

您可以通过写入 .* 来请求复合值的全部字段:

(compositecol).*

此表示法根据上下文的不同表现不同;有关详细信息,请参阅第 8.16.5 节

4.2.5. 运算符调用 #

运算符调用的语法有两种可能性:

表达式 运算符 表达式 (二元中缀运算符)
运算符 表达式 (一元前缀运算符)

其中 运算符 标记遵循第 4.1.3 节的语法规则,或者是一个关键字 ANDORNOT,或者是一个限定运算符名称,形式如下:

OPERATOR(schema.operatorname)

哪些运算符存在以及它们是单目还是双目取决于系统或用户定义了哪些运算符。第 9 章描述了内置运算符。

4.2.6. 函数调用 #

函数调用的语法是函数名(可能带有模式名限定),后跟用括号括起来的参数列表:

function_name ([expression [, expression ... ]] )

例如,以下计算 2 的平方根:

sqrt(2)

内置函数的列表在第 9 章中。用户可以添加其他函数。

在发出查询时,如果某些用户不信任其他用户,请在编写函数调用时注意第 10.3 节中的安全注意事项。

参数可以选择性地附加名称。有关详细信息,请参阅第 4.3 节

注意

接受复合类型单个参数的函数可以选择性地使用字段选择语法调用,反之亦然,字段选择也可以写成函数样式。也就是说,col(table)table.col 的表示法是可互换的。此行为不是 SQL 标准,但 PostgreSQL 提供此功能,因为它允许使用函数来模拟“计算字段”。有关更多信息,请参阅第 8.16.5 节

4.2.7. 聚合表达式 #

聚合表达式表示在查询选择的行上应用聚合函数。聚合函数将多个输入减少为一个输出值,例如输入的总和或平均值。聚合表达式的语法是以下之一:

aggregate_name (expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name (ALL expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name (DISTINCT expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name ( * ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name ( [ expression [ , ... ] ] ) WITHIN GROUP ( order_by_clause ) [ FILTER ( WHERE filter_clause ) ]

其中 aggregate_name 是先前定义的聚合(可能带有模式名限定),expression 是任何不包含聚合表达式或窗口函数调用的值表达式。可选的 order_by_clausefilter_clause 稍后描述。

聚合表达式的第一种形式对每个输入行调用一次聚合。第二种形式与第一种相同,因为 ALL 是默认值。第三种形式对输入行中找到的表达式的每个不同值(或多个表达式的集合)调用一次聚合。第四种形式对每个输入行调用一次聚合;由于未指定特定输入值,因此通常仅对 count(*) 聚合函数有用。最后一种形式用于 有序集聚合函数,稍后将进行描述。

大多数聚合函数会忽略 NULL 输入,因此其中一个或多个表达式产生 NULL 的行将被丢弃。除非另有说明,否则可以假设所有内置聚合函数都是如此。

例如,count(*) 返回输入行的总数;count(f1) 返回 f1 非 NULL 的输入行的数量,因为 count 会忽略 NULL;而 count(distinct f1) 返回 f1 的不同非 NULL 值的数量。

通常,输入行以未指定顺序送入聚合函数。在许多情况下,这并不重要;例如,无论 min 接收输入的顺序如何,它都会产生相同的结果。但是,一些聚合函数(如 array_aggstring_agg)产生的结果取决于输入行的顺序。使用此类聚合时,可选的 order_by_clause 可用于指定所需的顺序。order_by_clause 具有与查询级 ORDER BY 子句相同的语法,如第 7.5 节中所述,只是其表达式始终只是表达式,不能是输出列名或数字。例如:

WITH vals (v) AS ( VALUES (1),(3),(4),(3),(2) )
SELECT array_agg(v ORDER BY v DESC) FROM vals;
  array_agg
-------------
 {4,3,3,2,1}

由于 jsonb 只保留最后一个匹配的键,因此其键的排序可能很重要:

WITH vals (k, v) AS ( VALUES ('key0','1'), ('key1','3'), ('key1','2') )
SELECT jsonb_object_agg(k, v ORDER BY v) FROM vals;
      jsonb_object_agg
----------------------------
 {"key0": "1", "key1": "3"}

处理多参数聚合函数时,请注意 ORDER BY 子句位于所有聚合参数之后。例如,这样写:

SELECT string_agg(a, ',' ORDER BY a) FROM table;

而不是这样:

SELECT string_agg(a ORDER BY a, ',') FROM table;  -- incorrect

后者在语法上是有效的,但它表示一个带两个 ORDER BY 键的单参数聚合函数调用(第二个键因为是常量而相当无用)。

如果在 DISTINCT 中指定了 order_by_clause,则 ORDER BY 表达式只能引用 DISTINCT 列表中的列。例如:

WITH vals (v) AS ( VALUES (1),(3),(4),(3),(2) )
SELECT array_agg(DISTINCT v ORDER BY v DESC) FROM vals;
 array_agg
-----------
 {4,3,2,1}

ORDER BY 放置在聚合的常规参数列表中(如目前所述),用于对通用和统计聚合的输入行进行排序,对于这些聚合,排序是可选的。存在一类称为 有序集聚合的聚合函数,对于这些函数 order_by_clause必需的,通常是因为聚合的计算仅在特定输入行顺序的意义上才有意义。有序集聚合的典型示例包括排名和百分位数计算。对于有序集聚合,order_by_clause 写入 WITHIN GROUP (...) 中,如上最终语法选项所示。 order_by_clause 中的表达式与常规聚合参数一样,为每个输入行计算一次,按照 order_by_clause 的要求排序,并作为输入参数提供给聚合函数。(这与非 WITHIN GROUP order_by_clause 的情况不同,后者不被视为聚合函数的参数。) WITHIN GROUP 之前的表达式(如果有)称为 直接参数,以区别于 order_by_clause 中列出的 聚合参数。与常规聚合参数不同,直接参数仅为每个聚合调用计算一次,而不是为每个输入行计算一次。这意味着它们只能包含变量,前提是这些变量由 GROUP BY 分组;此限制与直接参数不在聚合表达式内部时相同。直接参数通常用于每种聚合计算只作为一个有意义的值的情况,例如百分位数分数。直接参数列表可以为空;在这种情况下,只写 () 而不是 (*)。(PostgreSQL 实际上会接受任一拼写,但只有第一种符合 SQL 标准。)

有序集聚合调用的一个例子是:

SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY income) FROM households;
 percentile_cont
-----------------
           50489

households 表中获取 income 列的第 50 百分位数,即中位数。这里 0.5 是一个直接参数;百分位数分数作为变化的行值是没有意义的。

如果指定了 FILTER,那么只有 filter_clause 求值为 true 的输入行才会被送入聚合函数;其他行将被丢弃。例如:

SELECT
    count(*) AS unfiltered,
    count(*) FILTER (WHERE i < 5) AS filtered
FROM generate_series(1,10) AS s(i);
 unfiltered | filtered
------------+----------
         10 |        4
(1 row)

预定义的聚合函数在第 9.21 节中描述。用户可以添加其他聚合函数。

聚合表达式只能出现在 SELECT 命令的结果列表或 HAVING 子句中。禁止在其他子句(如 WHERE)中使用,因为这些子句在聚合结果形成之前在逻辑上进行了计算。

当聚合表达式出现在子查询中时(请参阅第 4.2.11 节第 9.24 节),聚合通常在子查询的行上进行求值。但是,如果聚合的参数(以及可选的 filter_clause)只包含外部级别变量,则会出现例外:聚合属于最近的外部级别,并在该查询的行上进行求值。然后,整个聚合表达式成为它出现的子查询的外部引用,并在该子查询的任何一次求值中充当常量。关于只能出现在结果列表或 HAVING 子句中的限制适用于聚合所属的查询级别。

4.2.8. 窗口函数调用 #

窗口函数调用表示在查询选择的行的一部分上应用类似聚合的函数。与非窗口聚合调用不同,这不与将选定行分组为单个输出行相关联——每个行在查询输出中保持独立。但是,窗口函数可以访问属于当前行组的所有行,根据窗口函数调用的分组规范(PARTITION BY 列表)。窗口函数调用的语法是以下之一:

function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER window_name
function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )
function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER window_name
function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )

其中 window_definition 具有以下语法:

[ existing_window_name ]
[ PARTITION BY expression [, ...] ]
[ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ frame_clause ]

可选的 frame_clause 可以是以下之一:

{ RANGE | ROWS | GROUPS } frame_start [ frame_exclusion ]
{ RANGE | ROWS | GROUPS } BETWEEN frame_start AND frame_end [ frame_exclusion ]

其中 frame_startframe_end 可以是以下之一:

UNBOUNDED PRECEDING
offset PRECEDING
CURRENT ROW
offset FOLLOWING
UNBOUNDED FOLLOWING

frame_exclusion 可以是以下之一:

EXCLUDE CURRENT ROW
EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS

此处,expression 代表任何不包含窗口函数调用的值表达式。

window_name 是对查询 WINDOW 子句中定义的命名窗口规范的引用。或者,可以在括号内给出完整的 window_definition,使用与在 WINDOW 子句中定义命名窗口相同的语法;有关详细信息,请参阅SELECT 参考页。值得指出的是,OVER wnameOVER (wname ...) 并不完全等价;后者表示复制和修改窗口定义,如果引用的窗口规范包含框架子句,则会被拒绝。

PARTITION BY 子句将查询的行分组到 分区中,窗口函数分别处理这些分区。PARTITION BY 的工作方式类似于查询级的 GROUP BY 子句,只是它的表达式始终只是表达式,不能是输出列名或数字。如果没有 PARTITION BY,则将查询产生的所有行视为单个分区。ORDER BY 子句确定分区中行的处理顺序。它的工作方式类似于查询级的 ORDER BY 子句,但同样不能使用输出列名或数字。如果没有 ORDER BY,则行以未指定顺序处理。

frame_clause 指定构成 窗口帧的行集,这是当前分区的一个子集,适用于作用于帧而不是整个分区的窗口函数。帧中的行集可能因当前行而异。帧可以以 RANGEROWSGROUPS 模式指定;在每种情况下,它从 frame_start 运行到 frame_end。如果省略 frame_end,则默认值为 CURRENT ROW

UNBOUNDED PRECEDINGframe_start 意味着帧从分区的第一个行开始,同样,UNBOUNDED FOLLOWINGframe_end 意味着帧以分区的最后一个行结束。

RANGEGROUPS 模式下,CURRENT ROWframe_start 意味着帧从当前行的第一个 对等行(窗口 ORDER BY 子句将其排序为与当前行等效的行)开始,而 CURRENT ROWframe_end 意味着帧以当前行的最后一个对等行结束。在 ROWS 模式下,CURRENT ROW 仅表示当前行。

offset PRECEDINGoffset FOLLOWING 帧选项中,offset 必须是一个不包含任何变量、聚合函数或窗口函数的表达式。offset 的含义取决于帧模式:

  • ROWS 模式下,offset 必须产生一个非 NULL、非负整数,并且该选项意味着帧从当前行之前或之后指定的行数开始或结束。

  • GROUPS 模式下,offset 必须再次产生一个非 NULL、非负整数,并且该选项意味着帧在当前行的对等组之前或之后指定的 对等组数量开始或结束,其中对等组是与 ORDER BY 排序等效的行集。(要在 GROUPS 模式下使用,窗口定义中必须有一个 ORDER BY 子句。)

  • RANGE 模式下,这些选项要求 ORDER BY 子句指定正好一个列。offset 指定该列在当前行中的值与帧的前导或后续行中的值之间的最大差值。offset 表达式的数据类型取决于排序列的数据类型。对于数值排序列,它通常与排序列具有相同的类型,但对于日期时间排序列,它是一个 interval。例如,如果排序列是 datetimestamp 类型,可以写成 RANGE BETWEEN '1 day' PRECEDING AND '10 days' FOLLOWINGoffset 仍要求非 NULL 且非负,尽管“非负”的含义取决于其数据类型。

在任何情况下,到帧末尾的距离都受限于到分区末尾的距离,因此对于分区末尾附近的行,帧可能包含比其他地方少的行。

请注意,在 ROWSGROUPS 模式下,0 PRECEDING0 FOLLOWING 都等同于 CURRENT ROW。这通常在 RANGE 模式下也成立,并且具有相应数据类型的“零”的含义。

frame_exclusion 选项允许从帧中排除当前行周围的行,即使它们会根据帧开始和帧结束选项被包含在内。EXCLUDE CURRENT ROW 排除当前行。EXCLUDE GROUP 排除当前行及其排序对等行。EXCLUDE TIES 排除当前行的任何对等行,但不包括当前行本身。EXCLUDE NO OTHERS 仅显式指定不排除当前行或其对等行的默认行为。

默认的帧选项是 RANGE UNBOUNDED PRECEDING,它等同于 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。使用 ORDER BY 时,这会将帧设置为从分区开始到当前行的最后一个 ORDER BY 对等行。没有 ORDER BY 时,这意味着帧包含分区的全部行,因为所有行都成为当前行的对等行。

限制是 frame_start 不能是 UNBOUNDED FOLLOWINGframe_end 不能是 UNBOUNDED PRECEDING,并且 frame_end 的选择不能出现在上述 frame_startframe_end 选项列表的顺序靠前于 frame_start 的选择——例如,不允许 RANGE BETWEEN CURRENT ROW AND offset PRECEDING。但是,例如,允许 ROWS BETWEEN 7 PRECEDING AND 8 PRECEDING,即使它永远不会选择任何行。

如果指定了 FILTER,则只有 filter_clause 求值为 true 的输入行才会被送入窗口函数;其他行将被丢弃。只有是聚合的窗口函数才接受 FILTER 子句。

内置窗口函数在表 9.67中描述。用户可以添加其他窗口函数。此外,任何内置或用户定义的通用或统计聚合都可以用作窗口函数。(有序集和假设集聚合目前不能用作窗口函数。)

使用 * 的语法用于将无参数的聚合函数作为窗口函数调用,例如 count(*) OVER (PARTITION BY x ORDER BY y)。星号 (*) 通常不用于特定于窗口的函数。特定于窗口的函数不允许在函数参数列表中使用 DISTINCTORDER BY

窗口函数调用仅允许在查询的 SELECT 列表和 ORDER BY 子句中。

有关窗口函数的更多信息,请参阅第 3.5 节第 9.22 节第 7.2.5 节

4.2.9. 类型转换 #

类型转换指定从一种数据类型到另一种数据类型的转换。PostgreSQL 接受两种等效的类型转换语法:

CAST ( expression AS type )
expression::type

CAST 语法符合 SQL;带有 :: 的语法是历史上的 PostgreSQL 用法。

当类型转换应用于已知类型的常量表达式时,它表示运行时类型转换。只有当定义了合适的类型转换操作时,转换才会成功。请注意,这与常量上的转换用法略有不同,如第 4.1.2.7 节所示。应用于纯字符串文字的转换表示字面常量值的初始类型分配,因此它将适用于任何类型(前提是字符串文字的内容对于数据类型是可接受的输入语法)。

如果对值表达式必须产生的类型没有歧义(例如,当它被赋给表列时),则通常可以省略显式类型转换;系统在这种情况下会自动应用类型转换。但是,自动转换仅用于在系统目录中标记为“OK to apply implicitly”的转换。其他转换必须使用显式转换语法调用。此限制旨在防止静默应用意外的转换。

也可以使用函数式语法指定类型转换:

typename ( expression )

但是,这仅适用于名称也有效作为函数名称的类型。例如,double precision 不能这样使用,但等效的 float8 可以。此外,由于语法冲突,名称 intervaltimetimestamp 只能以这种方式使用,如果它们被双引号括起来。因此,函数式转换语法的用法会导致不一致,并且可能应避免。

注意

函数式语法实际上只是一个函数调用。当使用两种标准转换语法之一进行运行时转换时,它将内部调用已注册的函数来执行转换。按照惯例,这些转换函数与它们的输出类型同名,因此“函数式语法”只不过是对底层转换函数的直接调用。显然,这不是可移植应用程序应该依赖的内容。有关更多详细信息,请参阅CREATE CAST

4.2.10. 排序规则表达式 #

COLLATE 子句覆盖表达式的排序规则。它附加到它所应用的表达式:

expr COLLATE collation

其中 collation 是一个可能带有模式限定的标识符。 COLLATE 子句比运算符绑定得更紧;必要时可以使用括号。

如果没有显式指定排序规则,则数据库系统要么从表达式涉及的列中推导出排序规则,要么如果表达式不涉及任何列,则默认为数据库的默认排序规则。

COLLATE 子句的两个常见用途是覆盖 ORDER BY 子句中的排序顺序,例如:

SELECT a, b, c FROM tbl WHERE ... ORDER BY a COLLATE "C";

以及覆盖具有区域设置敏感结果的函数或运算符调用的排序规则,例如:

SELECT * FROM tbl WHERE a > 'foo' COLLATE "C";

请注意,在后一种情况下,COLLATE 子句附加到我们希望影响的运算符的输入参数上。 COLLATE 子句附加到哪个运算符或函数调用的参数上并不重要,因为运算符或函数应用的排序规则是通过考虑所有参数推导出来的,并且显式的 COLLATE 子句将覆盖所有其他参数的排序规则。(然而,将不匹配的 COLLATE 子句附加到多个参数上是错误的。有关更多详细信息,请参阅第 23.2 节。)因此,这与前面的示例结果相同:

SELECT * FROM tbl WHERE a COLLATE "C" > 'foo';

但这将是错误的:

SELECT * FROM tbl WHERE (a > 'foo') COLLATE "C";

因为它试图将排序规则应用于 > 运算符的结果,该结果是不可排序的数据类型 boolean

4.2.11. 标量子查询 #

标量子查询是括号中的普通 SELECT 查询,它返回恰好一行,恰好一列。(有关编写查询的信息,请参阅第 7 章。)执行 SELECT 查询,并使用返回的单个值作为周围值表达式的一部分。使用返回多行或多列的查询作为标量子查询是错误的。(但是,如果在特定执行期间,子查询未返回任何行,则不会出错;标量结果将视为 NULL。)子查询可以引用周围查询中的变量,这些变量在子查询的任何一次求值中都将充当常量。另请参阅第 9.24 节,了解涉及子查询的其他表达式。

例如,以下查找每个州人口最多的城市:

SELECT name, (SELECT max(pop) FROM cities WHERE cities.state = states.name)
    FROM states;

4.2.12. 数组构造器 #

数组构造器是一个表达式,它使用其成员元素的值来构建数组值。简单的数组构造器由关键字 ARRAY、左方括号 [、表达式列表(用逗号分隔)作为数组元素值,最后是右方括号 ] 组成。例如:

SELECT ARRAY[1,2,3+4];
  array
---------
 {1,2,7}
(1 row)

默认情况下,数组元素类型是成员表达式的公共类型,使用与 UNIONCASE 结构相同的规则确定(请参阅第 10.5 节)。您可以通过显式将数组构造器转换为所需类型来覆盖此设置,例如:

SELECT ARRAY[1,2,22.7]::integer[];
  array
----------
 {1,2,23}
(1 row)

这与单独将每个表达式转换为数组元素类型具有相同效果。有关类型转换的更多信息,请参阅第 4.2.9 节

多维数组值可以通过嵌套数组构造器来构建。在内部构造器中,可以省略关键字 ARRAY。例如,以下产生相同的结果:

SELECT ARRAY[ARRAY[1,2], ARRAY[3,4]];
     array
---------------
 {{1,2},{3,4}}
(1 row)

SELECT ARRAY[[1,2],[3,4]];
     array
---------------
 {{1,2},{3,4}}
(1 row)

由于多维数组必须是矩形的,因此同一级别的内部构造器必须生成相同维度的子数组。应用于外部 ARRAY 构造器的任何转换都会自动传播到所有内部构造器。

多维数组构造器元素可以是任何产生正确类型的数组的内容,不仅仅是子 ARRAY 构造器。例如:

CREATE TABLE arr(f1 int[], f2 int[]);

INSERT INTO arr VALUES (ARRAY[[1,2],[3,4]], ARRAY[[5,6],[7,8]]);

SELECT ARRAY[f1, f2, '{{9,10},{11,12}}'::int[]] FROM arr;
                     array
------------------------------------------------
 {{{1,2},{3,4}},{{5,6},{7,8}},{{9,10},{11,12}}}
(1 row)

您可以构建一个空数组,但由于无法创建没有类型的数组,因此必须显式将空数组转换为所需的类型。例如:

SELECT ARRAY[]::integer[];
 array
-------
 {}
(1 row)

也可以从子查询的结果构建数组。在此形式中,数组构造器用关键字 ARRAY 后跟一个括号括起来的(不是方括号)子查询编写。例如:

SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%');
                              array
------------------------------------------------------------------
 {2011,1954,1948,1952,1951,1244,1950,2005,1949,1953,2006,31,2412}
(1 row)

SELECT ARRAY(SELECT ARRAY[i, i*2] FROM generate_series(1,5) AS a(i));
              array
----------------------------------
 {{1,2},{2,4},{3,6},{4,8},{5,10}}
(1 row)

子查询必须返回单个列。如果子查询的输出列是非数组类型,则生成的(一维)数组将为子查询结果中的每一行包含一个元素,其元素类型与子查询的输出列的类型匹配。如果子查询的输出列是数组类型,则结果将是一个相同维度但高一维的数组;在这种情况下,所有子查询行都必须生成相同维度的数组,否则结果将不是矩形的。

使用 ARRAY 构建的数组值下标总是从 1 开始。有关数组的更多信息,请参阅第 8.15 节

4.2.13. 行构造器 #

行构造器是一个表达式,它使用其成员字段的值来构建行值(也称为复合值)。行构造器由关键字 ROW、左括号、零个或多个表达式(用逗号分隔)作为行字段值,最后是右括号组成。例如:

SELECT ROW(1,2.5,'this is a test');

当列表中有一个以上表达式时,关键字 ROW 是可选的。

行构造器可以包含 rowvalue.* 语法,它将被展开为行值元素的列表,就像在 SELECT 列表的顶层使用 .* 语法时一样(请参阅第 8.16.5 节)。例如,如果表 t 有列 f1f2,那么以下内容是相同的:

SELECT ROW(t.*, 42) FROM t;
SELECT ROW(t.f1, t.f2, 42) FROM t;

注意

PostgreSQL 8.2 之前,.* 语法不会在行构造器中展开,因此写入 ROW(t.*, 42) 会创建一个两字段行,其第一个字段是另一个行值。新行为通常更有用。如果您需要旧的嵌套行值行为,请在不带 .* 的情况下写入内部行值,例如 ROW(t, 42)

默认情况下,由 ROW 表达式创建的值是匿名记录类型。如有必要,它可以转换为命名复合类型——表行类型或使用 CREATE TYPE AS 创建的复合类型。为了避免歧义,可能需要显式转换。例如:

CREATE TABLE mytable(f1 int, f2 float, f3 text);

CREATE FUNCTION getf1(mytable) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;

-- No cast needed since only one getf1() exists
SELECT getf1(ROW(1,2.5,'this is a test'));
 getf1
-------
     1
(1 row)

CREATE TYPE myrowtype AS (f1 int, f2 text, f3 numeric);

CREATE FUNCTION getf1(myrowtype) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;

-- Now we need a cast to indicate which function to call:
SELECT getf1(ROW(1,2.5,'this is a test'));
ERROR:  function getf1(record) is not unique

SELECT getf1(ROW(1,2.5,'this is a test')::mytable);
 getf1
-------
     1
(1 row)

SELECT getf1(CAST(ROW(11,'this is a test',2.5) AS myrowtype));
 getf1
-------
    11
(1 row)

行构造器可用于构建要存储在复合类型表列中的复合值,或传递给接受复合参数的函数。此外,还可以使用标准比较运算符(如第 9.2 节中所述)测试行,以将一行与另一行进行比较(如第 9.25 节中所述),并在子查询中使用它们(如第 9.24 节中所讨论)。

4.2.14. 表达式求值规则 #

子表达式的求值顺序未定义。特别是,运算符或函数的输入不一定按从左到右或任何其他固定顺序求值。

此外,如果表达式的结果可以通过仅求值其中的一部分来确定,则其他子表达式可能根本不被求值。例如,如果有人写:

SELECT true OR somefunc();

那么 somefunc() (可能)根本不会被调用。如果有人写:

SELECT somefunc() OR true;

请注意,这与某些编程语言中的布尔运算符的从左到右“短路”不同。

因此,将具有副作用的函数用作复杂表达式的一部分是不明智的。WHEREHAVING 子句中的副作用或求值顺序尤其危险,因为这些子句在制定执行计划时会被大量重新处理。这些子句中的布尔表达式(AND/OR/NOT 组合)可以以布尔代数定律允许的任何方式重新组织。

当强制求值顺序至关重要时,可以使用 CASE 构造(请参阅第 9.18 节)。例如,在 WHERE 子句中尝试避免除以零的方法是不可信的:

SELECT ... WHERE x > 0 AND y/x > 1.5;

但这很安全:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

以这种方式使用的 CASE 构造将挫败优化尝试,因此只有在必要时才应执行。(在此特定示例中,最好通过写入 y > 1.5*x 来规避该问题。)

CASE 并不是解决此类问题的万能药。上述技术的一个限制是它不能防止常量子表达式的早期求值。如第 36.7 节所述,标记为 IMMUTABLE 的函数和运算符可以在查询规划时而不是执行时进行求值。因此,例如:

SELECT CASE WHEN x > 0 THEN x ELSE 1/0 END FROM tab;

可能会导致除零错误,因为规划器会尝试简化常量子表达式,即使表中的每一行都有 x > 0,使得 ELSE 分支在运行时永远不会被进入。

虽然这个特定的例子可能看起来很愚蠢,但涉及常量的相关情况可能发生在函数内执行的查询中,因为函数参数和局部变量的值可以作为常量插入到查询中进行规划。例如,在 PL/pgSQL 函数中,使用 IF-THEN-ELSE 语句来保护有风险的计算比将其嵌套在 CASE 表达式中安全得多。

同样类型的另一个限制是 CASE 不能阻止其中包含的聚合表达式的求值,因为聚合表达式在考虑 SELECT 列表或 HAVING 子句中的其他表达式之前进行计算。例如,以下查询可能会导致除零错误,尽管看似已受到保护:

SELECT CASE WHEN min(employees) > 0
            THEN avg(expenses / employees)
       END
    FROM departments;

min()avg() 聚合函数在所有输入行上同时计算,因此如果任何行的 employees 为零,则除零错误将在 min() 结果有机会被测试之前发生。相反,使用 WHEREFILTER 子句可以防止有问题的输入行到达聚合函数。

提交更正

如果您在文档中看到任何不正确、与您对特定功能的使用经验不符或需要进一步澄清的内容,请使用此表单报告文档问题。