4.2. 值表达式

值表达式用在各种语法环境中,比如在SELECT命令的目标列表中, 在INSERTUPDATE中用作新的列值, 或者在许多命令的搜索条件中使用。我们有时候把值表达式的结果叫做标量, 以便与一个表表达式的结果相区别(是一个表)。因此值表达式也叫做 标量表达式(或简称表达式)。 表达式语法允许对来自基本部分的数值进行算术、逻辑、集合、和其它运算。

值表达式是下列内容之一:

除了这个列表以外,还有许多构造可以归类为表达式,但是不遵循任何通用的语法规则。 它们通常有函数或操作符的语义,并且在第 9 章里合适的位置描述。 一个例子是IS NULL子句。

我们已经在第 4.1.2 节里讨论过常量了。下面的节讨论剩下的选项。

4.2.1. 字段引用

一个字段可以用下面的形式引用:

correlation.columnname

correlation是一个表的名字(可能有模式修饰), 或者是用FROM子句这样的方法定义的表的别名。 如果在当前查询所使用的所有表中,该字段名字是唯一的, 那么这个相关名字(correlation)和分隔用的点就可以省略(参见第 7 章)。

4.2.2. 位置参数

位置参数引用用于标识从外部给 SQL 语句的参数。参数用于 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]

(这里的方括号[ ]按照字面文本的方式出现。) 每个subscript自己都是一个表达式,它必须生成一个整数值。

通常,数组expression必须用圆括弧包围, 但如果只是一个字段引用或者一个位置参数,那么圆括弧可以省略。同样, 如果源数组是多维的,那么多个下标可以连接在一起。比如:

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

最后一个例子里的圆括弧是必须的。参阅第 8.15 节获取有关数组的更多信息。

4.2.4. 字段选择

如果一个表达式生成一个复合类型(行类型),那么用下面的方法可以抽取一个指定的字段

expression.fieldname

通常,行expression必须用圆括弧包围, 但是如果要选取的表达式只是一个表引用或者位置参数,可以省略圆括弧。比如:

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

因此,一个全称的字段引用实际上只是一个字段选择语法的特例。 一个重要的特殊情形是提取的表列是一个复合型的字段:

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

在这里,括号是必须的,用来指出compositecol是列名而不是表名, mytable是表名而不是模式名。

在一个选择列表中(查看第 7.3 节), 你可以通过使用.*来要求所有的组合值字段。

(compositecol).*

4.2.5. 操作符调用

操作符调用有三种语法:

expression operator expression (双目中缀操作符)
operator expression (单目前缀操作符)
expression operator (单目后缀操作符)

这里的operator记号遵循第 4.1.3 节的语法规则, 或者是记号ANDORNOT之一。 或者是一个被修饰的操作符名:

OPERATOR(schema.operatorname)

具体存在哪个操作符以及它们是单目还是双目取决于系统或用户定义了什么操作符。 第 9 章描述了内置的操作符。

4.2.6. 函数调用

函数调用的语法是合法函数名(可能有模式名修饰),后面跟着包含参数列表的圆括弧:

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

比如,下面的代码计算 2 的平方根:

sqrt(2)

内置函数的列表在第 9 章里。其它函数可由用户添加。

可选的可附加名字的参数,详细请参阅第 4.3 节

注意: 一个接受一个复合类型参数的函数,可以使用字段选择语法调用,相反的, 字段选择可以用函数的风格写出来。也就是说,符号col(table)table.col是可以互换的。这个行为不是SQL标准,但是由 PostgreSQL提供,因为它允许函数使用仿真"计算域"。 获取更多信息,请参阅第 35.4.3 节

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(*)聚合函数。 最后一种形式和ordered-set聚合函数(在下面描述)一起使用。

大多数的聚合函数忽略了NULL输入,因此在一个或多个表达式中产生NULL的行会被丢弃。 对所有的内置聚合函数而言,这样做是可以的,除非另行说明。

比如,count(*)生成输入行的总数;count(f1)生成 f1不为 NULL 的输入行数,因为count忽略NULL; count(distinct f1)生成f1唯一且非 NULL 的行数。

一般情况下,输入行会以非特定顺序放入到聚合函数中。在许多情况下,这样做是没有影响的; 如,无论以什么顺序输入,min输出相同的结果。然而, 一些聚合函数(如array_aggstring_agg)并非如此。 当使用这种聚合函数时,可以用order_by_clause选项指定输入的顺序。 除了它的表达式仅仅只是表达式,并且不能输出列名或列数之外,order_by_clauseORDER 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 BYPostgreSQL的一个扩展。

在聚合的常规参数列表中放置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_disc(0.5) WITHIN GROUP (ORDER BY income) FROM households;
 percentile_disc
-----------------
           50489

它获取来自表householdsincome 字段的百分之50或者说中位数。这里的0.5是一个直接参数; 它对于在不同行之间变化的百分比分数来说没有任何意义。

如果指定了FILTER,那么仅有 filter_clause计算为真的输入行供给聚合函数; 丢弃其他行。例如:

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.20 节里描述。其它聚合函数可以由用户增加。

一个聚合表达式只能在SELECT命令的结果列表或者HAVING子句里出现。 禁止在其它子句里出现(比如WHERE子句),因为这些子句逻辑上在生成聚合结果之前计算。

如果一个聚合表达式出现在一个子查询里(参阅第 4.2.11 节第 9.22 节),聚合通常是在子查询中进行计算。 但是如果聚合的参数(和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 } frame_start
{ RANGE | ROWS } BETWEEN frame_start AND frame_end

frame_startframe_end可以是:

UNBOUNDED PRECEDING
value PRECEDING
CURRENT ROW
value FOLLOWING
UNBOUNDED FOLLOWING

在这里,expression 表示的是任何自己不含窗口调用函数的值表达式。

window_name引用的是查询语句中WINDOW 子句定义的命名窗口规范。可替代的,可以在括号中给出一个完整的 window_definition,使用在WINDOW 子句中定义命名窗口相同的语法;参阅SELECT查看更多资料。 有必要指出OVER wname并不完全等同于OVER (wname); 后者意味着拷贝和修改窗口定义,并且如果引用的窗口声明包括一个框架子句, 则将被拒绝。

PARTITION BY选项将查询的行分为一组进入partitions, 这些行在窗口函数中单独处理。PARTITION BY和查询级别GROUP BY 子句做相似的工作,除了它的表达式只能作为表达式不能作为输出列的名字或数。 没有PARTITION BY,所有由查询产生的行被视为一个单独的分区。ORDER BY 选项决定分区中的行被窗口函数处理的顺序。它和查询级别ORDER BY子句做相似的工作, 但是同样的它不能作为输出列的名字或数。没有ORDER BY,行以一个不被预知的顺序处理。

对这些窗口函数(在这个框架而不是整个分区上的), frame_clause指定构成window frame的行, 他们是当前分区的一个子集。框架可以用RANGEROWS模式声明;不管哪种情况, 它的变化范围是从frame_startframe_end。如果省略了frame_end 默认为CURRENT ROW

一个frame_startUNBOUNDED PRECEDING意味着框架从分区中的第一行开始, 相似的,一个frame_endUNBOUNDED FOLLOWING意味着框架从分区中的最后一行结束。

RANGE模式中,frame_startCURRENT ROW 意味着框架从当前行的第一个peer行开始(ORDER BY 认为等于当前行的行),而frame_endCURRENT ROW 意味着框架从最后一个同等的ORDER BY行结束。在ROWS模式中, CURRENT ROW 简单的意味着当前行。

value PRECEDINGvalue FOLLOWING 当前只允许ROWS模式。这也就意味着,框架从当前行之前或之后指定的行数启动或结束。 value必须是整型表达式,而不能包含变量,聚合函数,或者窗口函数。 该值不能为空或负,但可以是零,表示只选择当前行本身。

默认的框架选项是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_start选项早—例如 RANGE BETWEEN CURRENT ROW AND value PRECEDING是不被允许的。

如果声明了FILTER,那么只有 filter_clause评估为真的输入行被送入窗口函数; 丢弃其他行。只有聚合的窗口函数接受FILTER子句。

内置窗口函数在表 9-53中有描述。其他窗口函数, 用户可以自己添加。同样,任意内置或用户自定义的普通聚合函数可以同窗口函数一样使用。 顺序集聚合目前不能用作窗口函数。

使用*的语法可以用来调用无参数的聚合函数为窗口函数,如 count(*) OVER (PARTITION BY x ORDER BY y)。星号(*) 通常不用于非聚合的窗口函数。与通常的聚合函数不同,聚合窗口函数不允许在函数参数列中使用 DISTINCTORDER BY

窗口调用函数只能在SELECT列,或者查询的ORDER BY子句中使用。

更多关于窗口函数的信息可以参考: 第 3.5 节第 9.21 节第 7.2.4 节

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子句到多个参数, 是一个错误。更多详细信息请参阅第 22.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.22 节以获取其它包含子查询的表达式。

比如,下面的查询找出每个州中的最大人口数量的城市:

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,2413}
(1 row)

子查询必须只返回一个单独的字段。生成的一维数组将为子查询里每行结果生成一个元素, 元素类型匹配子查询的输出字段。

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

4.2.13. 行构造器

行构造器是一个从提供给它的成员字段数值中构造行值(也叫复合类型值)的表达式。 一个行构造器由关键字ROW、一个左圆括弧、零个或多个作为行字段值的表达式(用逗号分隔)、 一个右圆括弧组成。比如:

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

如果在列表里有多个表达式,那么关键字ROW是可选的。

行构造器可以包含rowvalue.*语法, 它将被扩展为行值元素的列表,就像将.*语法用于一个SELECT 列表顶层一样。例如,如果表tf1f2两个字段, 那么下面两句是等价的:

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;


-- 因为只有一个getf1()存在,所以不需要类型转换
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;

-- 现在我们需要类型转换以表明调用哪个函数:
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.23 节。 行构造器还可以用于连接子查询,这些在第 9.22 节里面有详细讨论。

4.2.14. 表达式计算规则

子表达式的计算顺序是未定义的。特别要指出的是, 一个操作符或者函数的输入并不一定是按照从左向右的顺序或者以某种特定的顺序进行计算的。

另外,如果一个表达式的结果可以通过只判断它的一部分就可以得到, 那么其它子表达式就可以完全不计算了。比如,如果我们这么写:

SELECT true OR somefunc();

那么somefunc()就(可能)根本不会被调用。 即使像下面这样写也是一样:

SELECT somefunc() OR true;

请注意这和某些编程语言里从左向右"短路"布尔操作符是不一样的。

因此,拿有副作用的函数作为复杂表达式的一部分是不明智的。 在WHEREHAVING子句里依赖副作用或者是计算顺序是特别危险的, 因为这些子句都是作为生成一个执行规划的一部分进行了大量的再处理。 在这些子句里的布尔表达式(AND/OR/NOT 的组合)可以用布尔代数运算律允许的任何方式进行识别。

如果需要强制计算顺序,那么可以使用CASE构造(参阅第 9.17 节)。 比如,下面是一种企图避免在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不是这种问题的万灵药。 上面已经说明了的该技术的一个限制是,它不阻止常量表达式的早期计算。 就像第 35.6 节中描述的那样, 标记为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等于0, 那么被零除错误将在有机会测试min()的结果之前发生。代替的, 在第一个位置使用WHEREFILTER 子句阻止有问题的输入行到达聚合函数。