Skip to content

4. SQL 语法

本章介绍 SQL 的语法。它为理解以下章节奠定了基础,这些章节将详细介绍如何应用 SQL 命令来定义和修改数据。

我们还建议已经熟悉 SQL 的用户仔细阅读本章,因为它包含一些规则和概念,这些规则和概念在 SQL 数据库中实现不一致或特定于 PostgreSQL。

4.1. 词法结构

SQL 输入由一系列命令组成。命令由一系列标记组成,以分号(“;”)结尾。输入流的末尾也会终止命令。哪些标记有效取决于特定命令的语法。

标记可以是关键字、标识符、带引号的标识符、文本(或常量)或特殊字符符号。标记通常由空格(空格、制表符、换行符)分隔,但如果没有歧义,则不必如此(通常仅当特殊字符与其他标记类型相邻时才出现这种情况)。

例如,以下是(语法上)有效的 SQL 输入:

sql
SELECT * FROM MY_TABLE;
UPDATE MY_TABLE SET A = 5;
INSERT INTO MY_TABLE VALUES (3, 'hi there');

这是一个由三个命令组成的序列,每行一个(尽管这不是必需的;一行可以有多个命令,并且命令可以有效地拆分到各行)。

此外,SQL 输入中可能会出现注释。它们不是标记,它们实际上等同于空格。

SQL 语法在哪些标记标识命令以及哪些是操作数或参数方面不是很一致。前几个标记通常是命令名称,因此在上面的示例中,我们通常会使用“SELECT”、“UPDATE”和“INSERT”命令。但是,例如 UPDATE 命令总是需要一个 SET 标记出现在某个位置,而这个特定的 UPDATE 变体也需要一个 VALUES 来完成。每个命令的精确语法规则在 第 VI 部分 中描述。

4.1.1. 标识符和关键字

在上面的示例中,诸如 SELECTUPDATEVALUES 等标记是关键字的示例,即在 SQL 语言中具有固定含义的单词。标记 MY_TABLEA 是标识符的示例。它们标识表、列或其他数据库对象的名称,具体取决于使用它们的命令。因此,它们有时被简单地称为“名称”。关键词和标识符具有相同的词法结构,这意味着如果不了解语言,就无法知道标记是标识符还是关键字。完整的关键词列表可以在 附录 C 中找到。

SQL 标识符和关键字必须以字母(a - z,但也可以以带有变音符号和非拉丁字母的字母)或下划线( _ )开头。标识符或关键字中的后续字符可以是字母、下划线、数字(0 - 9 或美元符号( $ )。请注意,根据 SQL 标准的字母,标识符中不允许使用美元符号,因此使用美元符号可能会降低应用程序的可移植性。SQL 标准不会定义包含数字或以下划线开头或结尾的关键字,因此这种形式的标识符是安全的,不会与标准的未来扩展发生冲突。

系统使用的标识符不超过 NAMEDATALEN-1 个字节;较长的名称可以写在命令中,但它们将被截断。默认情况下,NAMEDATALEN 为 64,因此最大标识符长度为 63 字节。如果此限制有问题,可以通过更改 src/include/pg_config_manual.h 中的 NAMEDATALEN 常量来提高它。

关键字和不带引号的标识符不区分大小写。因此:

sql
UPDATE MY_TABLE SET A = 5;

可以等效地写成:

sql
uPDaTE my_TabLE SeT a = 5;

通常使用的惯例是用大写字母写关键词,用小写写字母写名称,例如:

sql
UPDATE my_table SET a = 5;

还有第二种标识符:带分隔符的标识符或带引号的标识符。它是通过将任意字符序列括在双引号( " )中而形成的。分隔标识符始终是标识符,而不是关键字。因此,可以用来指代名为“select”的列或表,而未加引号 select 的列或表将被视为关键字,因此 "select" 在需要表或列名称的地方使用时会引起解析错误。该示例可以使用带引号的标识符编写,如下所示:

sql
UPDATE "my_table" SET "a" = 5;

带引号的标识符可以包含任何字符,但代码为零的字符除外。(若要包含双引号,请写两个双引号。)这允许构造原本不可能的表名或列名,例如包含空格或与号的表名或列名。长度限制仍然适用。

引用标识符也会使其区分大小写,而未引用的名称始终折叠为小写。例如,PostgreSQL 认为标识符 FOOfoo"foo" 是相同的,但 "Foo""FOO" 与这三个不同。(在 PostgreSQL 中,将不带引号的名称折叠为小写字母与 SQL 标准不兼容,该标准规定未带引号的名称应折叠为大写。因此,根据标准,foo 应该等同于 "FOO" 而不是 "foo"。如果你想编写可移植的应用程序,建议你总是引用一个特定的名称,或者永远不要引用它。)

带引号的标识符的变体允许包含由其代码点标识的转义 Unicode 字符。此变体以 U&(大写或小写 U 后跟 &)开头,紧接着开始的双引号,中间没有任何空格,例如 U&"foo"。(注意,这就造成了与运算符 & 的歧义。在运算符周围使用空格以避免此问题。)在引号内,Unicode 字符可以以转义形式指定,方法是写一个反斜杠,后跟四位十六进制码位数,或者写一个反斜杠,后跟一个加号,后跟一个六位十六进制码位数。例如,标识符 "data" 可以写为:

txt
U&"d\0061t\+000061"

下面这个不那么琐碎的例子用西里尔字母写了俄语单词:

txt
U&"\0441\043B\043E\043D"

如果需要一个不同于反斜杠的转义字符,可以在字符串后使用 UESCAPE 子句指定,例如:

txt
U&"d!0061t!+000061" UESCAPE '!'

转义字符可以是除十六进制数字、加号、单引号、双引号或空白字符以外的任何单个字符。请注意,转义字符在 UESCAPE 之后用单引号而不是双引号表示。

要在标识符中包含转义字符,请将其写入两次。

4 位或 6 位转义形式都可以用来指定 UTF-16 代理编码对,以组成码位大于 U+FFFF 的字符,尽管 6 位形式的可用性在技术上使其不必要。(代理编码对不直接存储,而是组合到单个代码点中。)

如果服务器编码不是 UTF-8,则由这些转义序列之一标识的 Unicode 代码点将转换为实际的服务器编码;如果无法转换,则报告错误。

4.1.2. 常量

PostgreSQL 中有三种隐式类型的常量:字符串、位字符串和数字。常量也可以用显式类型来指定,这可以使系统能够更准确地表示和更有效地处理。这些备选方案将在以下小节中讨论。

4.1.2.1. 字符串常量

SQL 中的字符串常量是由单引号(')限制的任意字符序列,例如 'This is a string'。要在字符串常量中包含单引号字符,请写两个相邻的单引号,例如,'Dianne''s horse'。请注意,这与双引号字符(")不同。

两个字符串常量仅用空格分隔,且至少有一个换行符,这两个字符串常量将被连接起来,并有效地处理为就像字符串已被写入为一个常量一样。举例来说:

sql
SELECT 'foo'
'bar';

相当于:

sql
SELECT 'foobar';

但是:

sql
SELECT 'foo'      'bar';

不是有效语法。(这个有点奇怪的行为是由SQL指定的; PostgreSQL遵循标准。)

4.1.2.2. 带 C 风格转义的字符串常量

PostgreSQL 也接受转义字符串常量通过在开始单引号前写字母 E(大写或小写)来指定,例如,E'foo'. (当跨行继续转义字符串常量时,仅在第一个左引号之前写入 E。)在转义字符串中,反斜杠字符(\)开始一个类似 C 的反斜杠转义序列,其中反斜杠和后面的字符组合表示一个特殊的字节值,如表 4.1 所示。

表 4.1 反斜杠转义序列

转义序列说明
\b退格
\f换页
\n换行符
\r回车
\t制表符
\o, \oo, \ooo (o = 0–7)八进制字节值
\xh, \xhh (h = 0–9, A–F)十六进制字节值
\uxxxx, \Uxxxxxxxx (x = 0–9, A–F)16 位或 32 位十六进制 Unicode 字符值

反斜杠后面的任何其他字符都是按字面意思理解的。因此,要包含反斜杠字符,请写两个反斜杠(\\)。此外,除了 的正常方式外,还可以通过编写 \' 在转义字符串中包含单引号 ''

您有责任将创建的字节序列(尤其是在使用八进制或十六进制转义符时)组成服务器字符集编码中的有效字符。一个有用的替代方法是使用 Unicode 转义或替代 Unicode 转义语法,如 第 4.1.2.3 节 所述;然后服务器将检查字符转换是否可行。

警告

如果配置参数 standard_conforming_strings 是 off ,则 PostgreSQL 可识别常规常量和转义字符串常量中的反斜杠转义。但是,从 PostgreSQL 9.1 开始,默认值为 on ,这意味着反斜杠转义仅在转义字符串常量中被识别。此行为更符合标准,但可能会破坏依赖于历史行为的应用程序,在这些行为中,反斜杠转义始终被识别。解决方法是,可以将此参数设置为 off ,但最好不要使用反斜杠转义符。如果需要使用反斜杠转义符来表示特殊字符,请用 E。

除了 之外 standard_conforming_strings ,配置参数 escape_string_warningbackslash_quote 还控制字符串常量中反斜杠的处理。

代码为零的字符不能位于字符串常量中。

4.1.2.3. 带有 Unicode 转义的字符串常量

PostgreSQL 还支持另一种类型的字符串转义语法,允许按码位指定任意 Unicode 字符。Unicode 转义字符串常量以紧接在左引号之前的 U& 开头(大写或小写字母 U 后跟 & 符号),中间没有任何空格,例如 U&'foo' 。(请注意,这会与运算符 & 产生歧义。在运算符周围使用空格可避免此问题。在引号内,可以通过写一个反斜杠后跟一个四位数的十六进制码位号,或者一个反斜杠后跟一个加号,后跟一个六位十六进制码位数,以转义形式指定 Unicode 字符。例如,字符串 'data' 可以写成:

txt
U&'d\0061t\+000061'

下面这个不太平凡的例子用西里尔字母写了俄语单词“slon”(大象):

txt
U&'\0441\043B\043E\043D'

如果需要与反斜杠不同的转义字符,可以使用字符串后面的 UESCAPE 子句指定该转义字符,例如:

txt
U&'d!0061t!+000061' UESCAPE '!'

转义字符可以是除十六进制数字、加号、单引号、双引号或空格字符以外的任何单个字符。

若要从字面上将转义字符包含在字符串中,请将其写入两次。

4 位或 6 位转义形式可用于指定 UTF-16 代理编码对,以组成码位大于 U+FFFF 的字符,尽管 6 位形式的可用性在技术上使这变得没有必要。(代理编码对不直接存储,而是组合到单个代码点中。)

如果服务器编码不是 UTF-8,则由这些转义序列之一标识的 Unicode 码位将转换为实际的服务器编码;如果无法做到这一点,则会报告错误。

此外,字符串常量的 Unicode 转义语法仅在配置参数 standard_conforming_strings 打开时才有效。这是因为此语法可能会混淆解析 SQL 语句的客户端,从而导致 SQL 注入和类似的安全问题。如果参数设置为 off,则此语法将被拒绝并显示错误消息。

4.1.2.4. 以美元为引号的字符串常量

虽然指定字符串常量的标准语法通常很方便,但当所需的字符串包含许多单引号时,可能很难理解,因为每个引号都必须加倍。为了在这种情况下允许更具可读性的查询,PostgreSQL 提供了另一种称为“美元引用”的方式来编写字符串常量。以美元引用的字符串常量由一个美元符号( $ ),一个由零个或多个字符组成的可选“标签”,另一个美元符号,构成字符串内容的任意字符序列,与此美元引号开头的标签相同,还有一个美元符号组成。例如,使用美元引号指定字符串“Dianne's horse”有两种不同的方法:

txt
$$Dianne's horse$$
$SomeTag$Dianne's horse$SomeTag$

请注意,在以美元引号的字符串中,可以使用单引号而无需转义。事实上,以美元引号括起来的字符串内的任何字符都不会被转义:字符串内容始终按字面意思编写。反斜杠并不特殊,美元符号也不特殊,除非它们是与开始标记匹配的序列的一部分。

可以通过在每个嵌套级别选择不同的标记来嵌套以美元引号引号的字符串常量。这最常用于编写函数定义。例如:

sql
$function$
BEGIN
    RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
END;
$function$

在这里,序列 $q$[\t\r\n\v\\]$q$ 表示一个以美元为引号的字符串,当 PostgreSQL 执行函数体时,该字符串 [\t\r\n\v\\] 将被识别。但是由于序列与外部美元引号分隔符 $function$ 不匹配,就外部字符串而言,它只是常量中的一些字符。

带美元引号的字符串的标记(如果有)遵循与不带引号的标识符相同的规则,只是它不能包含美元符号。标记区分大小写,因此 $tag$String content$tag$ 是正确的,但 $TAG$String content$tag$ 事实并非如此。

关键字或标识符后面的带美元引号的字符串必须用空格分隔;否则,美元引号分隔符将被视为前面标识符的一部分。

美元引号不是 SQL 标准的一部分,但与符合标准的单引号语法相比,它通常是编写复杂字符串文字的更方便的方法。当在其他常量中表示字符串常量时,它特别有用,这在过程函数定义中经常需要。使用单引号语法时,上述示例中的每个反斜杠都必须写成四个反斜杠,在解析原始字符串常量时,这些反斜杠将减少到两个反斜杠,然后在函数执行期间重新解析内部字符串常量时减少到一个反斜杠。

4.1.2.5. 位字符串常量

位字符串常量看起来像常规字符串常量,在开头引号之前有一个 B(大写或小写,没有中间空格),例如 B'1001'。位字符串常量中唯一允许的字符是 01

或者,可以使用前导 X(大写或小写)以十六进制表示法指定位字符串常量,例如 X'1FF'。此表示法等效于位字符串常量,每个十六进制数字有四个二进制数字。

这两种形式的位字符串常量都可以与常规字符串常量相同的方式跨行延续。美元引号不能在位字符串常量中使用。

4.1.2.6. 数值常量

数值常量采用以下一般形式:

txt
digits
digits.[digits][e[+-]digits]
[digits].digits[e[+-]digits]
digitse[+-]digits

其中 digits 是一个或多个十进制数字(0 到 9)。如果使用小数点之前或之后,必须至少有一个数字。指数标记( e )后面必须至少有一个数字(如果存在)。常量中不能嵌入任何空格或其他字符,但下划线除外,下划线可用于视觉分组,如下所述。请注意,任何前导加号或减号实际上并不被视为常量的一部分;它是应用于常量的运算符。

以下是有效数值常量的一些示例:

txt
42
3.5
4.
.001
5e2
1.925e-3

此外,非十进制整数常量以下列形式接受:

txt
0xhexdigits
0ooctdigits
0bbindigits

其中 hexdigits 是一个或多个十六进制数字 (0-9,A-F),octdigits 是一个或多个八进制数字 0-7), bindigits 是一个或多个二进制数字(0 或 1)。十六进制数字和基数前缀可以是大写或小写。请注意,只有整数可以具有非十进制形式,而不能具有小数部分的数字。

以下是有效的非十进制整数常量的一些示例:

txt
0b100101
0B10011001
0o273
0O755
0x42f
0XFFFF

对于视觉分组,可以在数字之间插入下划线。这些对常量的值没有进一步的影响。例如:

txt
1_500_000_000
0b10001000_00000000
0o_1_755
0xFFFF_FFFF
1.618_034

不允许在数字常量或一组数字的开头或结尾(即,紧接在小数点或指数标记之前或之后)使用下划线,并且不允许在一行中使用多个下划线。

如果一个既不包含小数点也不包含指数的数值常量适合类型 integer(32 位),则最初假定其为类型 integer;否则,如果它的值适合类型 bigint(64 位),则假定它是类型 bigint;否则,它被视为类型 numeric。包含小数点和/或指数的常量最初始终假定为 numeric 类型 。

数值常量的初始分配数据类型只是类型解析算法的起点。在大多数情况下,常量将根据上下文自动强制转换为最合适的类型。必要时,可以通过强制转换数值来强制将其解释为特定数据类型。例如,可以通过编写以下命令强制将数值视为类型 real(float4):

sql
REAL '1.23'  -- string style
1.23::REAL   -- PostgreSQL (historical) style

这些实际上只是下面讨论的一般类型转换符号的特殊情况。

4.1.2.7. 其他类型的常量

可以使用以下任一表示法输入任意类型的常量:

sql
type 'string'
'string'::type
CAST ( 'string' AS type )

字符串常量的文本将传递给名为 type 的类型的输入转换例程。结果是指示类型的常量。如果常量必须是哪种类型没有歧义(例如,当它直接分配给表列时),则可以省略显式类型转换,在这种情况下,它会自动强制执行。

字符串常量可以使用常规 SQL 表示法或美元引号编写。

也可以使用类似函数的语法指定类型强制:

sql
typename ( 'string' )

但并非所有类型名称都可以以这种方式使用;有关详细信息,请参见 第 4.2.9 节

::CAST() 和函数调用语法也可用于指定任意表达式的运行时类型转换,如 第 4.2.9 节 所述。为了避免语法歧义,语法 type 'string' 只能用于指定简单文本常量的类型。type 'string' 语法的另一个限制是它不适用于数组类型;使用 ::CAST() 指定数组常量的类型。

CAST() 语法符合 SQL。 type 'string' 语法是标准的泛化:SQL 仅为少数数据类型指定此语法,但 PostgreSQL 允许所有类型使用此语法。的语法 :: 是 PostgreSQL 的历史用法,函数调用语法也是如此。

4.1.3. 运算符

运算符名称是以下列表中最多 NAMEDATALEN-1(默认为 63 个)字符的序列:

txt
+ - * / < > = ~ ! @ # % ^ & | ` ?

但是,对运算符名称有一些限制:

  • -- 并且 /* 不能出现在运算符名称中的任何位置,因为它们将被视为注释的开头。
  • 多字符运算符名称不能以 + 或 - 结尾,除非该名称还包含以下至少一个字符:
    txt
    ~ ! @ # % ^ & | ` ? ~ !@ # % ^ & |` ?
    例如,@- 是允许的运算符名称,*-则不允许。此限制允许 PostgreSQL 解析符合 SQL 的查询,而无需标记之间的空格。

使用非 SQL 标准运算符名称时,通常需要用空格分隔相邻的运算符以避免歧义。例如,如果定义了一个名为 @ 的前缀运算符,则不能写 X*@Y ;您必须编写 X* @Y 以确保 PostgreSQL 将其读取为两个运算符名称,而不是一个。

4.1.4. 特殊字符

某些非字母数字字符具有与运算符不同的特殊含义。有关用法的详细信息,请参阅描述相应语法元素的位置。本节的存在只是为了说明这些字符的存在并总结其目的。

  • 美元符号($)后跟数字用于表示函数定义或预准备语句主体中的位置参数。在其他上下文中,美元符号可以是标识符的一部分,也可以是以美元为引号的字符串常量的一部分。
  • 括号(())具有对表达式进行分组和强制优先级的通常含义。在某些情况下,括号是特定 SQL 命令的固定语法的一部分。
  • 括号([])用于选择数组的元素。有关数组的更多信息,请参见 第 8.15 节
  • 逗号(,)在某些语法结构中用于分隔列表的元素。
  • 分号(;)终止 SQL 命令。它不能出现在命令中的任何位置,除非出现在字符串常量或带引号的标识符中。
  • 冒号(:)用于从数组中选择“切片”。(参见 第 8.15 节)。在某些 SQL 方言(如嵌入式 SQL)中,冒号用于作为变量名称的前缀。
  • 星号(*)在某些上下文中用于表示表行或复合值的所有字段。当用作聚合函数的参数时,它还具有特殊含义,即聚合不需要任何显式参数。
  • 句点(.)用于数值常量,用于分隔架构、表和列名称。

4.1.5. 注释

注释是一系列字符,从双破折号开始延伸到行尾,例如:

sql
-- This is a standard SQL comment

或者,可以使用 C 样式的块注释:

sql
/* multiline comment
 * with nesting: /* nested block comment */
 */

其中注释以 /* 开头并延伸到匹配的 */。这些块注释嵌套在 SQL 标准中,但与 C 不同,因此可以注释掉可能包含现有块注释的较大代码块。

在进一步的语法分析之前,注释会从输入流中删除,并有效地替换为空格。

4.1.6. 运算符优先级

表 4.2 显示了 PostgreSQL 中运算符的优先级和关联性。大多数运算符具有相同的优先级,并且是左关联的。运算符的优先级和关联性硬连线到解析器中。如果希望以优先级规则所暗示的方式以外的其他方式分析具有多个运算符的表达式,请添加括号。

表 4.2 运算符优先级(从高到低)

运算符结合性描述
.表/列名分隔符
::PostgreSQL 风格的类型转换
[ ]数组元素选择
+ -一元加号,一元减号
^
* / %乘法、除法、取模
+ -加法、减法
其他运算符所有其他原生运算符和用户定义的运算符
BETWEEN IN LIKE ILIKE SIMILAR范围包含、设置成员身份、字符串匹配
< > = <= >= <>比较运算符
IS ISNULL NOTNULLIS TRUE,IS FALSE,IS NULL,IS DISTINCT FROM 等
NOT逻辑非
AND逻辑与
OR逻辑或

请注意,运算符优先级规则也适用于与上述内置运算符同名的用户定义运算符。例如,如果为某些自定义数据类型定义“+”运算符,则无论您的操作是什么,它都将与内置的“+”运算符具有相同的优先级。

OPERATOR 语法中使用架构限定的运算符名称时,例如:

sql
SELECT 3 OPERATOR(pg_catalog.+) 4;

OPERATOR 构造被视为具有表 4.2 中所示的“任何其他运算符”的默认优先级。无论内部 OPERATOR() 出现哪个特定的运算符,都是如此。

注意

9.5 之前的 PostgreSQL 版本使用的运算符优先级规则略有不同。特别是,<= >= 和 <> 过去被视为泛型操作符;过去,IS 测试具有更高的优先级;和 NOT BETWEEN 相关结构的行为不一致,在某些情况下被认为具有 NOT 而不是 BETWEEN 的优先级。更改这些规则是为了更好地遵从 SQL 标准,并减少对逻辑等价构造的不一致处理所造成的混淆。在大多数情况下,这些更改不会导致任何行为改变,或者可能导致“no such operator”失败,这可以通过添加括号来解决。然而,在一些极端情况下,查询可能会改变行为,而不会报告任何解析错误。

4.2. 值表达式

值表达式用于各种上下文中,例如在 SELECT 命令的目标列表中,作为 INSERTUPDATE 中的新列值,或在许多命令中的搜索条件中。值表达式的结果有时称为标量,以区别于表表达式的结果(即表)。因此,值表达式也称为标量表达式(甚至简称为表达式)。表达式语法允许使用算术、逻辑、集合和其他操作从基本部分计算值。

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

  • 常量值或文本值
  • 列引用
  • 位置参数引用,位于函数定义或预准备语句的主体中
  • 下标表达式
  • 字段选择表达式
  • 运算符调用
  • 函数调用
  • 聚合表达式
  • 窗口函数调用
  • 类型转换
  • 标量子查询
  • 数组构造函数
  • 行构造函数
  • 括号中的另一个值表达式(用于对子表达式进行分组并覆盖优先级)

除了此列表之外,还有许多构造可以归类为表达式,但不遵循任何通用语法规则。它们通常具有函数或运算符的语义,并在 第 9 章 的适当位置进行解释。一个例子是 IS NULL 子句。

我们已经在 第 4.1.2 节 中讨论过常量。以下各节讨论其余选项。

4.2.1. 列引用

列可以采用以下形式引用:

txt
correlation.columnname

correlation 是表的名称(可能使用模式名称限定),或者是通过 FROM 子句定义的表的别名。如果列名在当前查询中使用的所有表中是唯一的,则可以省略 correlation 和分隔点。(另见 第 7 章)。

4.2.2. 位置参数

位置参数引用用于指示外部提供给 SQL 语句的值。参数用于 SQL 函数定义和准备好的查询中。某些客户端库还支持将数据值独立于 SQL 命令字符串指定,在这种情况下,参数用于引用联机外数据值。参数引用的形式为:

txt
$number

例如,考虑函数 dept 的定义:

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

每当调用函数时,此处 $1 都会引用第一个函数参数的值。

4.2.3. 下标

如果表达式产生数组类型的值,则可以通过如下方式提取数组值的特定元素

txt
expression[subscript]

或者可以通过数组切片来提取多个相邻元素:

txt
expression[lower_subscript:upper_subscript]

(此处,括号 [ ] 应按字面意思显示)。每个 subscript 表达式本身就是一个表达式,该表达式将四舍五入到最接近的整数值。

通常,数组 expression 必须用括号括起来,但是当要下标的表达式只是列引用或位置参数时,可以省略括号。此外,当原始数组是多维数组时,可以连接多个下标。例如:

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

最后一个示例中的括号是必需的。有关数组的更多信息,请参见 第 8.15 节

4.2.4. 字段选择

如果表达式产生复合类型(行类型)的值,则可以通过如下方式提取该行的特定字段:

txt
expression.fieldname

通常,行 expression 必须用括号括起来,但是当要从中选择的表达式只是表引用或位置参数时,可以省略括号。例如:

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

(因此,限定的列引用实际上只是字段选择语法的特例)。一个重要的特殊情况是从复合类型的表列中提取字段:

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

此处需要括号来表示 compositecol 是列名而不是表名,或者 mytable 在第二种情况下是表名而不是模式名。

您可以通过以下方式 .* 请求复合值的所有字段:

txt
(compositecol).*

此表示法的行为因上下文而异;有关详细信息,请参见 第 8.16.5 节

4.2.5. 运算符调用

运算符调用有两种可能的语法:

  • expression operator expression(二元运算符)
  • operator expression (一元运算符)

其中标记 operator 遵循 第 4.1.3 节 的语法规则,或者是关键字 ANDORNOT 之一,或者是格式为以下形式的限定运算符名称:

txt
OPERATOR(schema.operatorname)

存在哪些特定的运算符以及它们是一元运算符还是二元运算符取决于系统或用户定义了哪些运算符。第 9 章 介绍了内置运算符。

4.2.6. 函数调用

函数调用的语法是函数的名称(可能使用模式名称限定),后跟括在括号中的参数列表:

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

例如,以下公式计算 2 的平方根:

txt
sqrt(2)

内置函数列表在 第 9 章 中。其他功能可由用户添加。

当在某些用户不信任其他用户的数据库中发出查询时,在编写函数调用时请遵守 第 10.3 节 中的安全预防措施。

参数可以选择附加名称。有关详细信息,请参见 第 4.3 节

注意

可以选择使用字段选择语法调用采用复合类型单个参数的函数,反之,字段选择可以采用函数式编写。也就是说,符号 col(table)table.col 是可以互换的。此行为不是 SQL 标准行为,而是在 PostgreSQL 中提供的,因为它允许使用函数来模拟“计算字段”。有关更多信息,请参见 第 8.16.5 节

4.2.7. 聚合表达式

聚合表达式表示聚合函数在查询选择的行中的应用。聚合函数将多个输入简化为单个输出值,例如输入的总和或平均值。聚合表达式的语法如下:

txt
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 为非空的输入行数,因为 Count 忽略空值; count(distinct f1) 生成 f1 中不同的非空值的个数。

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

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

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

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

而不是这样:

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

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

如果 DISTINCT 除了 order_by_clause 之外还指定了 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 分组的变量;此限制与直接参数根本不在聚合表达式中相同。直接参数通常用于百分位数分数等内容,这些分数仅作为每个聚合计算的单个值才有意义。直接参数列表可以为空;在这种情况下,请写 just () not (*)。(PostgreSQL 实际上会接受任何一种拼写,但只有第一种方式符合 SQL 标准。)

有序集聚合调用的一个示例如下:

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 的输入行提供给聚合函数;其他行将被丢弃。例如:

sql
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 子句中的限制适用于聚合所属的查询级别。

Released under the GPL-3.0 License.