值表达式

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

一个值表达式是下列之一:

  • 一个常量或者字面量
  • 一个列引用
  • 一个位置参数引用(在函数声明体中或预编写的语句中)
  • 一个下标表达式
  • 一个列选择表达式
  • 一个操作符调用
  • 一个函数调用
  • 一个聚合表达式
  • 一个类型转换
  • 一个标量子查询
  • 一个数组构造器
  • 一个行构造器
  • 一个在圆括弧里面的值表达式(可用于子表达式分组和覆盖优先级)。

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

列引用

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

correlation.columnname

correlation 是一个表的名字(可能是模式名的全称), 或者是用FROM子句这样的方法定义的表的别名,或者关键字OLD或NEW(其中NEW和OLD只能用于重写规则,而其他correlation都可以用于任何SQL表述)。 如果在当前查询所使用的所有表中,该列名字是唯一的, 那么这个相关名字(correlation)和分隔用的点就可以省略。

位置参数

位置参数引用用于标识从外部给 SQL 语句的参数。参数用于 SQL 函数定义语句和预编写的查询。有些客户端库还支持在 SQL 命令字符串外边声明数据值, 这种情况下参数用于引用 SQL 字符串行外的数据。一个参数的形式如下:

$number

比如下面这个dept函数的定义:

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

在函数被调用的时候这里的$1将引用第一个参数。

下标

如果一个表达式生成一个数组类型的数值, 那么我们可以通过下面这样的表达式来提取数组中的元素

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]

最后一个例子里的圆括弧是必须的。

字段选择

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

expression.fieldname

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

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

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

操作符调用

操作符调用有三种语法:

expression operator expression (双目中缀操作符)

operator expression (单目前缀操作符)

expression operator (单目后缀操作符)

这里的 operator 记号遵循 操作符 的语法规则,或者是关键词AND,OR,NOT之一。 或者是一个被修饰的操作符名:

OPERATOR(schema.operatorname)

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

函数调用

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

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

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

sqrt(2)

其它函数可由用户添加。

聚合表达式

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

aggregate_name (expression [ , ... ] [ order_by_clause ] )
aggregate_name (ALL expression [ , ... ] [ order_by_clause ] )
aggregate_name (DISTINCT expression [ , ... ] [ order_by_clause ] )
aggregate_name ( * )

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

第一种形式的聚合表达式为每个输入行调用聚合并得到非空值(事实上,它取决于聚合函数是否忽略空值,所有的标准函数都忽略)。第二种和第一种等价,因为ALL是缺省值。第三种形式为每个表达式中不同的值调用聚合。最后一种为每个输入无论是否为空调用聚合;因为没有声明特定的输入值,通常它只用于count(*)聚合函数。

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

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

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

类型转换

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

CAST ( expression AS type )
expression::type

CAST语法遵循 SQL 标准;::语法是OushuDB历史用法。

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

如果一个值表达式的值对某类型而言不存在混淆的情况,那么我们可以省略明确的类型转换(比如, 在给一个表列赋值的时候),而由系统自动执行类型转换。不过, 自动转换只适用于那些系统表中标记着”OK to apply implicitly”的转换函数。 其它转换函数必须明确地调用转换语法。这些限制是为了避免一些自动转换的应用。

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

typename( expression )

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

标量子查询

一个标量子查询是一个放在圆括弧里只返回一行一列的普通SELECT 查询。该SELECT 会被执行,而其返回值将在周围的值表达式中使用。把一个返回超过一行或者超过一列的查询用做标量查询是错误的。不过,在一个特定的表达式中,子查询不返回行则不算错误;标量结果被认为是 NULL。子查询可以引用外围查询的变量,这些变量在每次子查询中当做常量使用。

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

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

数组构造器

数组构造器是一个从自身成员元素上构造一个数组值的表达式。一个简单的数组构造器由关键字ARRAY、一个左方括弧 [、一个或多个表示数组元素值的表达式(用逗号分隔)、一个右方括弧] 组成。比如:

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

默认的,数组元素类型是成员表达式的公共类型,使用和UNION或 CASE构造一样的规则决定。多维数组值可以通过嵌套数组构造器的方法来制作。内层构造器中的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)

Note

MAGMA/PARQUET/ORC 格式的表不支持数组类型。

我们也可以从一个子查询的结果中构造一个数组。此时, 数组构造器是关键字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开始。

行构造器

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

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

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

缺省时,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 NULL 或IS 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

表达式计算规则

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

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

SELECT true OR somefunc();

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

SELECT somefunc() OR true;

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

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

如果需要强制计算顺序,那么可以使用CASE构造。比如,下面是一种企图避免在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更好。