4.2. 值表达式

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

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

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

我们已经在 Section 4.1.2 里有讨论过的内容了。下面的节讨论剩下的选项。

4.2.1. 字段引用

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

correlation.columnname

correlation 是一个表的名字(可能有模式修饰), 或者是用FROM子句这样的方法定义的表的别名,或者是关键字 NEWOLD。 (NEWOLD只能出现在一条改写规则中, 而其他相关的名字可以用于任意 SQL 语句中。) 如果在当前查询中所使用的所有表中,该字段名字是唯一的, 那么这个相关名字和分隔用的点就可以省略。 (又见 Chapter 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]

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

4.2.4. 字段选择

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

expression.fieldname

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

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

(因此,一个全称的字段引用实际上只是一个字段选择语法的特例。)

4.2.5. 操作符调用

操作符调用有三种语法∶

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

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

OPERATOR(schema.operatorname)

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

4.2.6. 函数调用

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

function ([expression [, expression ... ]] )

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

sqrt(2)

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

4.2.7. 聚集表达式

一个聚集表达式代表一个聚集函数对一个查询选出的行的处理。 一个聚集函数把多个输入缩减为一个输出值, 比如给输入求和或平均。一个聚集表达式的语法是下列之一:

aggregate_name (expression)
aggregate_name (ALL expression)
aggregate_name (DISTINCT expression)
aggregate_name ( * )

这里 aggregate_name 是前面定义的聚集,(可能是全称), 而 expression 是一个本身不包含聚集表达式的任意值表达式。

第一种形式的聚集表达式为所有表达式生成非空值的输入行调用聚集。 (实际上,是否忽略空值由聚集函数决定 --- 但是所有标准的聚集函数都忽略它们。) 第二种形式和第一种一样,因为 ALL 是缺省值。 第三种形式为所有输入行里找到表达式的所有唯一的非空值调用聚集。 最后一种形式为每个输入行(不管是空还是非空)调用一次聚集; 因为没有声明特定的输入值。通常它只是对 count() 聚集函数有用。

比如,count(*) 生成输入行的总数; count(f1) 生成 f1 为非空的输入行数; count(distinct f1) 生成 f1 唯一非空的行数。

预定义的聚集函数在 Section 9.15 里描述。 其它聚集函数可以由用户增加。

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

如果一个聚集表达式出现在一个子查询里(参阅 Section 4.2.9Section 9.16), 聚集通常是在子查询的行上进行计算。但是如果聚集的参数只包含外层查询的变量则有一个例外: 这个聚集会属于离他最近的外层查询,并且在该查询上进行计算。 该聚集表达式整体上属于它出现的子查询对外层查询的引用,其作用相当于子查询任何一次计算中的一个常量。 这个聚集表达式的有关只能出现在结果列或者 HAVING 子句的限制适用于聚集所属的查询层。

4.2.8. 类型转换

一个类型转换声明一个从一种数据类型到另外一种数据类型的转换。 PostgreSQL 接受两种等效的类型转换语法:

CAST ( expression AS type )
expression::type

CAST 语法遵循 SQL;:: 的语法是 PostgreSQL 传统用法。

如果对一个已知类型的值表达式应用转换,它代表一个运行时类型转换。 只有在存在合适的类型转换函数的情况下,该转换才能成功。 请注意这一点和用于常量的转换略有区别,如 Section 4.1.2.4 所示。 一个应用于某个未修饰的字串文本的转换表示给一个字串文本数值赋予一个初始化类型, 因此它对于任何类型都会成功(如果字串文本的内容符合该数据类型的输入语法接受。)

如果对于一个值表达式生成的数值对某类型而言不存在混淆的情况, 那么我们可以省略明确的类型转换(比如,在给一个表字段赋值的时候); 在这样的情况下,系统将自动附加一个类型转换。 不过,自动转换只适用于那些系统表中标记着 "OK to apply implicitly" 的转换函数。 其它转换函数必须用明确的转换语法调用。 这些限制是为了避免一些怪异的转换被应用。

我们也可以用函数样的语法声明一个类型转换:

typename ( expression )

不过,这个方法只能用于那些名字同时也是有效函数名字的类型。 比如,double precision 就不能这么用, 但是等效的 float8 可以。同样,intervaltime,和 timestamp 如果加了双引号也只能这么用, 因为存在语法冲突。因此,函数样的类型转换会导致不一致, 所以可能应该避免在新应用中这么用。 (函数样语法实际上就似乎一个函数调用。如果使用两种标准转换语法做运行时转换, 那么它将在内部调用一个已注册得函数执行转换。通常, 这种转换函数和它们得输出类型同名,但是这个要点可不是那些可以移植的程序可以依赖的东西。)

4.2.9. 标量子查询

一个标量子查询是一个放在圆括弧里的普通 SELECT查询, 它只返回只有一个字段的一行。(参阅 Chapter 7 获取有关写查询的信息。) 该 SELECT 将被执行, 而其单个返回值将在周围的值表达式中使用。 把一个返回超过一行或者超过一列的查询用做标量查询是错误的。 (不过,在特定的执行中,子查询不返回行则不算错误;标量结果认为是NULL。) 该子查询可以引用周围查询的变量,那些变量也是在计算任意子查询的时候当做常量使用的。 又见 Section 9.16

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

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

4.2.10. 数组构造器

一个数组构造器是一个表达式,它从它的成员元素上构造一个数组值。 一个简单的数组构造器由关键字 ARRAY,一个左方括弧 [, 一个或多个表达式(用逗号分隔)表示数组圆熟值,以及最后一个右方括弧 ]。 比如

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

数组元素类型是成员表达式的公共类型,使用和 UNIONCASE 构造一样的规则决定。 (参阅 Section 10.5)。

多维数组值可以通过嵌套数组构造器的方法来制作。 在内层构造器里,关键字 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 构造。 比如:

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)

我们也可以从一个子查询的结果中构造一个数组。在这种形式下, 数组构造器是用关键字 ARRAY 后面跟着一个用圆括弧(不是方括弧)包围的子查询。 比如:

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

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

ARRAY 建立的数组值的脚标总是从一开始。 有关数组的更多信息,参阅 Section 8.10

4.2.11. 表达式计算规则

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

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

SELECT true OR somefunc();

那么 somefunc() 就(可能)根本不会被调用。 如果我们写下面的,也可能会是这样

SELECT somefunc() OR true;

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

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

如果强制计算顺序非常重要,那么可以使用 CASE 构造(参阅 Section 9.12)。 比如,下面是一种视图避免在 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 更好。)