Skip to content

4.2. 值表达式#

4.2.1. 列引用
4.2.2. 位置参数
4.2.3. 下标
4.2.4. 字段选择
4.2.5. 运算符调用
4.2.6. 函数调用
4.2.7. 聚合表达式
4.2.8. 窗口函数调用
4.2.9. 类型转换
4.2.10. 排序规则表达式
4.2.11. 标量子查询
4.2.12. 数组构造函数
4.2.13. 行构造函数
4.2.14. 表达式求值规则

值表达式用于各种上下文,例如在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(*)聚合函数有用。最后一种形式与有序集聚合函数一起使用,如下所述。

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

例如,count(*)产生输入行的总数;count(f1)产生f1为非空的行数,因为count忽略空值;count(distinct f1)产生f1的不同非空值的数目。

通常,输入行以未指定顺序提供给聚合函数。在很多情况下,这无关紧要;例如,min无论以什么顺序接收输入,都会生成相同的结果。但是,某些聚合函数(例如array_aggstring_agg)会生成依赖于输入行顺序的结果。使用此类聚合时,可选的*order_by_clause可用于指定所需的顺序。order_by_clause*的语法与查询级别ORDER BY子句的语法相同,如第 7.5 节中所述,但其表达式始终只是表达式,不能是输出列名称或数字。例如

SELECT array_agg(a ORDER BY b DESC) FROM table;

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

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

而不是这样

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

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

如果除了*order_by_clause*之外还指定了DISTINCT,则所有ORDER BY表达式都必须与聚合的常规参数匹配;也就是说,您不能对未包含在DISTINCT列表中的表达式进行排序。

注意

在聚合函数中同时指定DISTINCTORDER BY的功能是PostgreSQL扩展。

如前所述,将ORDER BY放置在聚合的常规参数列表中,用于对通用和统计聚合的输入行进行排序,对于这些聚合,排序是可选的。有一种称为有序集聚合的聚合函数子类,它需要一个*order_by_clause,通常是因为聚合的计算仅在其输入行的特定排序方面才有意义。有序集聚合的典型示例包括排名和百分位数计算。对于有序集聚合,order_by_clause写在WITHIN GROUP (...)中,如上所示的最终语法备选方案中所示。order_by_clause中的表达式与常规聚合参数一样,每输入行评估一次,按照order_by_clause的要求进行排序,并作为输入参数馈送到聚合函数。(这与非WITHIN GROUPorder_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.23 节),聚合通常会在子查询的行上进行评估。但如果聚合的参数(以及*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 PRECEDING的*frame_start意味着框架从分区的第一个行开始,类似地,UNBOUNDED FOLLOWINGframe_end*意味着框架以分区的最后一行结束。

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

在*offsetPRECEDINGoffsetFOLLOWING框架选项中,offset必须是不包含任何变量、聚合函数或窗口函数的表达式。offset*的含义取决于框架模式

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

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

  • RANGE 模式中,这些选项要求 ORDER BY 子句精确指定一列。 offset 指定当前行中该列值与帧中前一行或后一行的值之间的最大差值。 offset 表达式的类型因排序列的类型而异。对于数字排序列,它通常与排序列的类型相同,但对于日期时间排序列,它是一个 interval。例如,如果排序列的类型为 datetimestamp,则可以编写 RANGE BETWEEN '1 day' PRECEDING AND '10 days' FOLLOWINGoffset 仍然必须为非空且非负,尽管 非负 的含义取决于其数据类型。

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

请注意,在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.64中进行了描述。用户可以添加其他窗口函数。此外,任何内置或用户定义的通用或统计聚合都可以用作窗口函数。(目前无法将有序集和假设集聚合用作窗口函数。)

使用*的语法用于将无参数聚合函数作为窗口函数进行调用,例如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 节中所示。应用于未加修饰的字符串文字的转换表示最初将类型分配给文字常量值,因此它将对任何类型成功(如果字符串文字的内容是数据类型的可接受输入语法)。

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

还可以使用类似函数的语法指定类型转换

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子句附加到多个参数是一个错误。有关更多详细信息,请参见第 24.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.23 节了解涉及子查询的其他表达式。

例如,以下查询找到每个州中最大的城市人口

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;

注意

在PostgreSQL8.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)

行构造器可用于构建要存储在复合类型表列中或传递给接受复合参数的函数的复合值。此外,可以比较两个行值或使用IS NULLIS NOT NULL测试行,例如

SELECT ROW(1,2.5,'this is a test') = ROW(1, 3, 'not the same');

SELECT ROW(table.*) IS NULL FROM table;  -- detect all-null rows

有关更多详细信息,请参阅第 9.24 节。行构造器还可用于与子查询一起使用,如第 9.23 节中所述。

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并不是此类问题的万能解决方案。上面说明的技术的一个限制是它不能防止常量子表达式的早期求值。如第 38.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子句来防止有问题的输入行首先到达聚合函数。