↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 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 命令的目标列表中,作为 INSERT 或 UPDATE 中的新列值,或者作为若干命令中的搜索条件。为了与表表达式(其结果是一张表)区分开来,值表达式的结果有时被称为标量。因此,值表达式也称为标量表达式(甚至简称为表达式)。表达式语法允许使用算术、逻辑、集合以及其他操作,从基本部分计算出值。

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

  • 一个常量或字面值

  • 一个列引用

  • 在一个函数定义体或预备语句中的一个位置参数引用

  • 一个下标表达式

  • 一个字段选择表达式

  • 一个操作符调用

  • 一个函数调用

  • 一个聚合表达式

  • 一个窗口函数调用

  • 一个类型转换

  • 一个排序规则表达式

  • 一个标量子查询

  • 一个数组构造器

  • 一个行构造器

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

在这个列表之外,还有一些结构可以被分类为一个表达式,但是它们不遵循任何一般语法规则。这些通常具有一个函数或操作符的语义并且在第 9 章中的合适位置解释。一个示例是 IS NULL 子句。

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

4.2.1. 列引用 #

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

correlation.columnname

correlation 是一个表的名字(可能带有模式名限定),或者是在 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]

(这里,方括号[ ] 表示其字面意思)。每一个 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 是一个表名而不是一个模式名。

你可以通过书写.* 来请求一个复合值的所有字段:

(compositecol).*

这种记法的行为根据上下文会有不同,详见第 8.16.5 节。

4.2.5. 操作符调用 #

对于一次操作符调用,有两种可能的语法:

expression operator expression(二元中缀操作符)
operator expression(一元前缀操作符)

其中 operator 词元遵循第 4.1.3 节的语法规则,或者是关键词 AND、OR 和 NOT 之一,或者是一个如下形式的模式限定操作符名:

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_clause 和 filter_clause 如下所述。

第一种形式的聚合表达式为每一个输入行调用一次聚合。第二种形式和第一种相同,因为 ALL 是默认选项。第三种形式为输入行中表达式的每一个非重复值(对于多个表达式,则是每一个非重复的值组合)调用一次聚合。第四种形式为每一个输入行调用一次聚合,因为没有特定的输入值被指定,它通常只对于 count(*) 聚合函数有用。最后一种形式被用于有序集聚合函数,其描述如下。

大部分聚合函数忽略值为 null 的输入,因此,一个或多个表达式的结果为 null 的行会被丢弃。除非另有说明,所有内置聚合都可认为具有这种行为。

例如,count(*) 得到输入行的总数。count(f1) 得到输入行中 f1 为非 null 的行数,因为 count 忽略空值。而 count(distinct f1) 得到 f1 的非 null 非重复值的数量。

通常,输入行会以未指定顺序传给聚合函数。很多情况下这无关紧要;例如,min 不论输入顺序如何,结果都相同。但某些聚合函数(例如 array_agg 和 string_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;  -- 不正确

后者在语法上是合法的,但它表示对单参数聚合函数的调用,并带有两个 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 求值为真的输入行才会传给聚合函数;其他行会被丢弃。例如:

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_start 和 frame_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 子句中定义命名窗口的相同语法在圆括号内给定一个完整的 window_definition,详见 SELECT 参考页。值得指出的是,OVER wname 并不严格地等价于 OVER (wname ...),后者表示复制并修改窗口定义,并且在被引用窗口声明包括一个帧子句时会被拒绝。

PARTITION BY 选项把查询中的行分组成分区,窗口函数会分别处理这些分区。PARTITION BY 的工作方式类似于查询级别的 GROUP BY 子句,不过它的表达式始终只是表达式,不能是输出列名或列编号。如果没有 PARTITION BY,该查询生成的所有行都会被当作单个分区处理。ORDER BY 选项决定窗口函数处理某个分区中的行时所采用的顺序。它的工作方式也类似于查询级别的 ORDER BY 子句,但同样不能使用输出列名或列编号。如果没有 ORDER BY,行将按未指定的顺序处理。

frame_clause 指定构成窗口帧的行集合,它是当前分区的一个子集,供那些作用于帧而不是整个分区的窗口函数使用。帧中的行集合会随着哪一行是当前行而变化。在 RANGE、ROWS 或者 GROUPS 模式中可以指定帧,在每一种情况下,帧的范围都是从 frame_start 到 frame_end。如果 frame_end 被省略,则末尾默认为 CURRENT ROW。

UNBOUNDED PRECEDING 的一个 frame_start 表示该帧开始于分区的第一行,类似地 UNBOUNDED FOLLOWING 的一个 frame_end 表示该帧结束于分区的最后一行。

在 RANGE 或 GROUPS 模式中,CURRENT ROW 的一个 frame_start 表示帧开始于当前行的第一个同等行(被窗口的 ORDER BY 子句排序为与当前行等效的行),而 CURRENT ROW 的一个 frame_end 表示帧结束于当前行的最后一个同等行。在 ROWS 模式中,CURRENT ROW 就表示当前行。

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

  • 在 ROWS 模式中,offset 必须得到一个非空、非负的整数,并且该选项表示帧开始或结束于当前行之前或者之后指定数量的行。

  • 在 GROUPS 模式中,offset 也必须得到一个非空、非负的整数,并且该选项表示帧开始或结束于当前行的同等行组之前或者之后指定数量的同等行组,这里同等行组是在 ORDER BY 顺序中等效的行集合(要使用 GROUPS 模式,在窗口定义中就必须有一个 ORDER BY 子句)。

  • 在 RANGE 模式中,这些选项要求 ORDER BY 子句正好指定一列。offset 指定当前行中那一列的值与它在该帧中前面或后面的行中的列值的最大差值。offset 表达式的数据类型会随着排序列的数据类型而变化。对于数字的排序列,它通常是与排序列相同的类型,但对于日期时间排序列它是一个 interval。例如,如果排序列是类型 date 或者 timestamp,我们可以写 RANGE BETWEEN '1 day' PRECEDING AND '10 days' FOLLOWING。offset 仍然要求是非空且非负,不过“非负”的含义取决于它的数据类型。

在任何一种情况下,到帧边界的距离都受限于到分区边界的距离,因此对于靠近分区两端的行来说,帧可能会包含比较少的行。

注意在 ROWS 以及 GROUPS 模式中,0 PRECEDING 和 0 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 FOLLOWING、frame_end 不能是 UNBOUNDED PRECEDING,并且在上述 frame_start 和 frame_end 选项的列表中 frame_end 选择不能早于 frame_start 选择出现 — 例如不允许 RANGE BETWEEN CURRENT ROW AND offset PRECEDING,但允许 ROWS BETWEEN 7 PRECEDING AND 8 PRECEDING,虽然它不会选择任何行。

如果指定了 FILTER,那么只有对 filter_clause 计算为真的输入行会被交给该窗口函数,其他行会被丢弃。只有是聚合的窗口函数才接受 FILTER。

内置的窗口函数在表 9.67 中介绍。用户可以加入其他窗口函数。此外,任何内置的或者用户定义的通用聚合或者统计性聚合都可以被用作窗口函数(有序集和假想集聚合当前不能被用作窗口函数)。

使用* 的语法被用来把无参数聚合函数当作窗口函数调用,例如 count(*) OVER (PARTITION BY x ORDER BY y)。星号(*)通常不被用于专用窗口函数。专用窗口函数不允许在函数参数列表中使用 DISTINCT 或 ORDER 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 可以。另外,由于语法冲突,名称 interval、time 和 timestamp 只有在使用双引号引用时才能采用这种写法。因此,函数风格的类型转换语法会带来不一致性,通常应尽量避免。

注意

函数风格的语法事实上只是一次函数调用。当两种标准类型转换语法之一被用来做一次运行时转换时,它将在内部调用一个已注册的函数来执行该转换。按照惯例,这些转换函数具有和它们的输出类型相同的名字,并且因此“函数风格的语法”无非是对底层转换函数的一次直接调用。显然,一个可移植的应用不应当依赖于它。详见 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 查询会被执行,其返回的单个值会用在外围值表达式中。把一个返回多于一行或多于一列的查询当作标量子查询来用是错误的。(但如果在某次具体执行中该子查询没有返回任何行,则不算错误;其标量结果会被视为空值。)该子查询可以引用外围查询中的变量,这些变量在该子查询的每一次求值期间都会作为常量。有关其他涉及子查询的表达式,另见第 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)

默认情况下,数组元素类型是成员表达式的公共类型,其确定规则与 UNION 或 CASE 结构相同(见第 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 有列 f1 和 f2,那么这些是相同的:

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)

行构造器可用于构造复合值,以存入复合类型表列,或传递给接收复合参数的函数。此外,还可以按第 9.2 节所述使用标准比较操作符测试行,按第 9.25 节所述比较两行,也可以按第 9.24 节所述与子查询结合使用。

4.2.14. 表达式计算规则 #

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

此外,如果一个表达式的结果可以通过只计算其一部分来决定,那么其他子表达式可能完全不需要被计算。例如,如果我们写:

SELECT true OR somefunc();

那么 somefunc() 将(可能)完全不被调用。如果我们写成下面这样也是一样:

SELECT somefunc() OR true;

注意这和一些编程语言中布尔操作符从左至右的“短路”不同。

因此,在复杂表达式中使用带有副作用的函数是不明智的。在 WHERE 和 HAVING 子句中依赖副作用或计算顺序尤其危险,因为在建立一个执行计划时这些子句会被广泛地重新处理。这些子句中布尔表达式(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() 的结果之前,就会发生除零错误。取而代之的是,可以使用一个 WHERE 或 FILTER 子句来首先阻止有问题的输入行到达一个聚合函数。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。英文原文本身的问题请通过上游文档表单反馈给 PostgreSQL 文档维护者。