PostgreSQL 18 中文参考文档(未翻译部分保留英文)🌐 切换到英文原版 →

4.1. 词法结构 #

4.1.1. 标识符与关键字
4.1.2. 常量
4.1.3. 运算符
4.1.4. 特殊字符
4.1.5. 注释
4.1.6. 运算符优先级

SQL 的输入由一系列命令(commands)组成。一条命令由一系列按序排列的记号(tokens)构成,并以分号(“;”)结尾。输入流的末尾同样代表命令的终结。哪些记号属于合法记号,完全取决于具体命令的语法结构。

一个记号可以是一个关键字(key word)、一个普通标识符(identifier)、一个引用标识符(quoted identifier)、一个字面量常量(literal),或者是一个特殊字符符号。记号之间通常使用空白字符(空格、制表符、换行符)进行分隔;但在不存在语法歧义的情况下(通常是当特殊字符与某种其他记号类型相邻时),并不强制要求必须有空白分隔。

例如,以下是语法上完全合法的 SQL 输入序列:

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

这是一组包含三条独立命令的序列,每行一条(但这并非格式强制要求;单行内可以书写多条命令,单条命令也可以按语义跨行排版)。

此外,SQL 输入中还可以包含注释(comments)。注释不属于命令记号,它们在词法分析阶段等价于空白字符。

SQL 语法在“哪些记号代表命令名称、哪些属于操作数或参数”的界定上并非绝对固定。前几个记号通常指代命令名称,因此在上例中我们通常习惯称呼其为 “SELECT” 命令、“UPDATE” 命令与 “INSERT” 命令。但例如 UPDATE 命令强制要求在特定语法位置出现 SET 记号,而上述形态的 INSERT 命令也必须包含 VALUES 记号才能构成完整的语句。各条具体命令的精确语法规则在 Part VI 中进行了逐一详尽阐述。

4.1.1. 标识符与关键字 #

在上例中出现的 SELECT、UPDATE 或 VALUES 等记号均属于关键字(key words),即在 SQL 语言规范中具有预定义固定语义的词汇。而记号 MY_TABLE 和 A 则是标识符(identifiers)的典型代表。根据它们在命令中所处的语法上下文,它们指代数据表、字段列或其他具体的数据库对象名称。因此,标识符有时也被简称为“名称”(names)。关键字与标识符遵循完全相同的词法结构,这意味着在脱离语言上下文的情况下,单纯看一个词汇记号是无法判定它是标识符还是关键字的。完整的官方关键字清单详见 Appendix C。

SQL 标识符与关键字必须以字母(a-z,同时也包括带有变音符号的字母以及非拉丁字符)或下划线(_)开头。标识符或关键字后续的字符可以是字母、下划线、阿拉伯数字(0-9)或美元符号($)。请注意,根据 SQL 国际标准的严格规定,标识符中并不允许使用美元符号,因此在应用中包含美元符号可能会降低代码在其他数据库系统间的可移植性。SQL 标准绝不会定义包含数字或以下划线开头/结尾的新关键字,因此采用这种形式命名的标识符可以有效防范未来与 SQL 标准新增关键字发生潜在命名冲突。

系统在底层最多识别标识符的前 NAMEDATALEN-1 个字节;在命令中可以键入更长的名称,但超长部分将被系统自动截断。在默认配置下,NAMEDATALEN 的取值为 64,因此标识符的最大有效长度为 63 字节。如果该限制无法满足特定业务场景,可以在编译前修改 src/include/pg_config_manual.h 中的常量值来调高上限。

关键字与未加双引号的普通标识符默认是大小写不敏感的。因此:

UPDATE MY_TABLE SET A = 5;

在语义上与以下书写方式完全等效:

uPDaTE my_TabLE SeT a = 5;

工程实践中广泛遵循的排版约定是将关键字全部大写、数据库对象名称全部小写,例如:

UPDATE my_table SET a = 5;

SQL 中还存在第二种标识符:界定标识符(delimited identifier)或称引用标识符(quoted identifier)。它是通过使用双引号(")将任意字符序列包裹起来而构成的。引用标识符在语法判定上永远被视作标识符,绝不会被误判为关键字。例如,"select" 可以合法用于指代名为 “select” 的数据表或字段列;而未加引号的 select 则会被直接识别为系统关键字,若将其置于期望表名或列名的位置将触发语法解析错误。前述示例改用引用标识符可写为:

UPDATE "my_table" SET "a" = 5;

引用标识符内部可以包含除编码为零(NUL 字符)之外的任何字符(若要在名称中包含双引号自身,只需连续书写两个双引号即可)。这使得构造包含空格、特殊标点或连字符等常规情况下非法的表名或列名成为可能。但无论是否加引号,63 字节的最大长度限制依然严格生效。

使用双引号包裹标识符还会使其具备大小写敏感性,而未加引号的普通名称在进入解析器时总是会被统一折叠转换为全小写。例如,标识符 FOO、foo 与 "foo" 在 PostgreSQL 看来是完全相同的同一个对象;但 "Foo" 与 "FOO" 则彼此互不相同,并且也与前三者不同(需要注意的是,PostgreSQL 将未加引号的名称默认折叠为全小写的行为与 SQL 国际标准不兼容,SQL 标准规定未加引号的名称应折叠为全大写。因此按照标准规定,foo 本应等价于 "FOO" 而非 "foo"。为了编写具备高度可移植性的应用,建议要么对特定对象名称始终坚持加双引号,要么全局始终不加双引号)。

引用标识符的一个高级变体允许通过字符的 Unicode 码点以转义序列的形式嵌入特殊字符。该语法以紧邻开双引号之前的 U&(大写或小写字母 U 紧跟与符号)作为前缀,中间严禁包含任何空格,例如 U&"foo"(请注意这可能会与按位与运算符 & 产生视觉歧义,建议在运算符两侧保留空格以规避该问题)。在双引号内部,Unicode 字符可以通过书写反斜杠紧跟 4 位十六进制码点数字来表示,或者以反斜杠紧跟加号及 6 位十六进制码点数字来表示。例如,标识符 "data" 可以等价书写为:

U&"d\0061t\+000061"

以下示例展示了以西里尔字母书写的俄语单词 “slon”(大象):

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

如果希望使用反斜杠以外的其他字符作为自定义转义符,可以在标识符后附加 UESCAPE 子句显式指定,例如:

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

转义符可以是除十六进制字符、加号、单引号、双引号或空白字符之外的任意单个字符。需要注意的是,在 UESCAPE 之后指定的转义符必须使用单引号包裹,而非双引号。

如果需要在标识符正文中包含转义字符本身的字面量,只需将其连续书写两次即可。

4 位或 6 位转义格式均可用于指定 UTF-16 代理对(surrogate pairs),以组合表示大于 U+FFFF 的高级码点字符(不过 6 位转义格式的引入在工程上已经使手动拼装代理对变得不再必要)。代理对在底层不会被直接存储,而是会被自动合成并还原为单个统一的 Unicode 码点。

如果服务器当前的底层字符集编码并非 UTF-8,那么通过这些转义序列声明的 Unicode 码点将被自动转换为服务器的实际字符集编码;如果目标字符集无法映射该字符,系统将抛出错误。

4.1.2. 常量 #

在 PostgreSQL 中存在三类具备隐式类型的常量:字符串常量、位串常量以及数值常量。常量也可以在书写时显式声明数据类型,显式类型能够使语义表达更加精确,并帮助优化器在解析阶段更高效地处理数据。以下各小节将逐一展开说明。

4.1.2.1. 字符串常量 #

SQL 中的普通字符串常量是由单引号(')包裹的任意字符序列,例如 'This is a string'。若要在字符串内部包含单引号本身,必须连续书写两个相邻的单引号,例如 'Dianne''s horse'。请牢记这绝非双引号字符(")。

两个仅由空白字符分隔、且其间至少包含一个换行符的相邻字符串常量,在语法分析时会被解析器自动拼接并视为单个统一的字符串常量。例如:

SELECT 'foo'
'bar';

完全等价于:

SELECT 'foobar';

但是:

SELECT 'foo'      'bar';

则是不合法的语法错误(这一在现代看来略显奇特的规则是由 SQL 国际标准规定的,PostgreSQL 严格遵守了该标准)。

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

PostgreSQL 还支持“转义”字符串常量,这是对 SQL 标准的一项实用扩展。转义字符串常量通过在起始单引号之前紧贴书写字母 E(大写或小写)来声明,例如 E'foo'(当转义字符串跨行排版时,仅需在首个单引号前添加 E)。在转义字符串内部,反斜杠字符(\)会开启 C 语言风格的反斜杠转义序列,反斜杠与后续字符的特定组合代表特殊的字节值,如 Table 4.1 所示。

Table 4.1. 反斜杠转义序列

反斜杠转义序列对应含义
\b退格符(Backspace)
\f换页符(Form Feed)
\n换行符(Newline)
\r回车符(Carriage Return)
\t水平制表符(Tab)
\o, \oo, \ooo (o = 0–7) 八进制字节值
\xh, \xhh (h = 0–9, A–F) 十六进制字节值
\uxxxx, \Uxxxxxxxx (x = 0–9, A–F) 16 位或 32 位十六进制 Unicode 字符值

反斜杠后面的任何其他字符都将被按字面原样理解。因此,若要在转义字符串中表示反斜杠本身,需要连续输入两个反斜杠(\\)。此外,除了标准的 '' 双单引号方式外,在转义字符串中也可以通过 \' 来直接表示单引号。

开发者有责任确保所构造的字节序列(尤其在使用八进制或十六进制转义时)在服务器当前的字符集编码下能够合成合法的有效字符。更为推荐的实践是使用 Unicode 转义语法(详见 Section 4.1.2.3),此时服务端会在解析阶段严格校验字符转换的合法性。

Caution

如果配置参数 standard_conforming_strings 被设置为 off,PostgreSQL 会在普通字符串和转义字符串中无差别地识别反斜杠转义。然而自 PostgreSQL 9.1 起,该参数的默认值已严格固定为 on,意味着反斜杠转义仅在显式声明了 E 前缀的转义字符串中才会被解析。这种行为完全符合 SQL 标准,但可能会影响那些依赖旧时代无前缀反斜杠历史习惯的存量应用。作为临时过渡方案可以将该参数置为 off,但强烈建议彻底迁移代码规范。在需要使用特殊转义字符时,必须显式以 E 声明字符串常量。

除 standard_conforming_strings 外,配置参数 escape_string_warning 与 backslash_quote 同样控制着字符串常量中反斜杠的解析与告警行为。

字符串常量中严禁包含编码为零的字节(NUL)。

4.1.2.3. Unicode 转义字符串常量 #

PostgreSQL 还支持另一种专用于字符串的转义语法,允许通过码点直接指定任意 Unicode 字符。Unicode 转义字符串常量在起始单引号之前紧贴添加前缀 U&(大写或小写字母 U 紧跟与符号),中间无空格,例如 U&'foo'(注意避免与二元运算符 & 产生解析混淆,建议在运算符两侧保留空格)。在单引号内部,Unicode 字符可以通过书写反斜杠紧跟 4 位十六进制码点,或反斜杠紧跟加号及 6 位十六进制码点来指定。例如,字符串 'data' 可以书写为:

U&'d\0061t\+000061'

以下示例以西里尔字母表示俄语单词 “slon”(大象):

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

如果希望使用反斜杠以外的其他字符作为自定义转义符,可在字符串后添加 UESCAPE 子句显式指定,例如:

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

转义字符可以是除十六进制字符、加号、单引号、双引号或空白字符之外的任意单字符。

若要在字符串中按字面含义包含转义符本身,连续书写两次即可。

4 位或 6 位转义格式均支持指定 UTF-16 代理对来合成大于 U+FFFF 的码点(但 6 位转义格式的存在在技术上使手动代理对显得多余)。代理对在底层会自动合并为单个码点存储。

如果服务器编码并非 UTF-8,转义序列所代表的 Unicode 码点将被转换为服务器的物理编码;无法转换时将抛出错误。

此外,Unicode 转义字符串语法仅在配置参数 standard_conforming_strings 为 on 时才能工作。这是因为在旧模式下,客户端词法分析器容易产生解析歧义并诱发潜在的 SQL 注入风险。若该参数为 off,此语法将被直接拒绝报错。

4.1.2.4. 美元符界定字符串常量 #

虽然标准的单引号字符串语法通常很方便,但在文本本身包含大量单引号时(例如嵌套代码或长文本),每个单引号都必须双写转义,这极大降低了代码的可读性。为此,PostgreSQL 提供了被称为“美元符界定”(dollar quoting)的高级字符串书写方式。一个美元符界定的字符串常量由一个美元符号($)、一个可选的零个或多个字符构成的“标签”(tag)、另一个美元符号、任意文本内容、一个美元符号、与开头完全相同的标签,以及一个闭合的美元符号组成。例如,声明字符串 “Dianne's horse” 的两种写法如下:

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

请注意,在美元符界定的文本内部,单引号可以直接书写而完全不需要转义。事实上,内部的任何字符都严禁转义:文本始终按绝对字面量存储。反斜杠不再具有特殊含义,内部的美元符号只要不与开头标签匹配,也会被按普通字符对待。

通过在不同嵌套层级选择不同的标签名,可以轻松实现美元符界定字符串的嵌套。这在编写复杂函数定义时尤为普遍:

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

在此,内部的 $q$[\t\r\n\v\\]$q$ 代表一个美元符界定的正则文本串 [\t\r\n\v\\],它将在函数执行时被正确识别。由于该序列与外层的界定符 $function$ 互不匹配,因此对外层字符串而言它只是一组普通的字面字符。

美元符界定字符串的标签命名规则遵循普通未加引号标识符的规范,但标签内部不能包含美元符号。标签严格区分大小写,因此 $tag$String content$tag$ 是合法的,而 $TAG$String content$tag$ 则属于未闭合的语法错误。

紧随关键字或标识符之后的美元符界定字符串必须通过空白字符隔开,否则美元前缀会被误解析为前面标识符的组成部分。

美元符界定语法并非 SQL 国际标准的组成部分,但在编写复杂文本(尤其是在存储过程或函数体内部定义另一段 SQL)时,它远比繁琐的单引号和成倍的反斜杠更清晰安全。若使用传统单引号,上述示例中的每个反斜杠在函数定义阶段必须层层倍增为 4 个反斜杠,极易产生编码疏漏。

4.1.2.5. 位串常量 #

位串常量类似于普通字符串常量,但在起始单引号前紧贴字母 B(大写或小写),中间无空格,例如 B'1001'。位串内部允许出现的字符仅限二进制数字 0 与 1。

位串常量也可以通过十六进制记法声明,前缀为字母 X(大写或小写),例如 X'1FF'。这种记法中每个十六进制位精确等价于 4 个二进制位。

这两种位串常量均支持像普通字符串那样跨行拼接书写。位串常量不支持美元符界定语法。

4.1.2.6. 数值常量 #

数值常量支持以下常规语法形式:

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

其中 digits 代表一个或多个十进制数字(0 到 9)。如果使用了小数点,小数点前后必须至少存在一个数字。如果包含科学计数法指数标记(e),其后必须紧跟至少一个数字。常量内部严禁包含空格或其他无关字符,但允许插入下划线用于长数字的可读性分组。请注意,开头的正负号在语法解析上不属于常量本身的构成部分,而是作用于该常量的独立一元运算符。

以下是合法的数值常量示例:


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

此外,系统还支持以下非十进制整型常量记法:

0xhexdigits
0ooctdigits
0bbindigits

分别代表十六进制(0x 前缀)、八进制(0o 前缀)与二进制(0b 前缀)。前缀与十六进制字母均不区分大小写。需要强调的是,非十进制记法仅支持整型数值,不支持带有小数部分的浮点数。

合法的非十进制整型常量示例如下:


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

为了增强大数字的可读性,可以在数字之间自由插入下划线作为视觉分组分隔符。下划线对常量的实际数值没有任何影响。例如:


1_500_000_000
0b10001000_00000000
0o_1_755
0xFFFF_FFFF
1.618_034

下划线不能出现在数值常量的开头或结尾,不能紧贴小数点或指数标记的前后,且严禁连续出现多个下划线。

一个既不包含小数点也不包含指数的普通数值常量,如果其数值在 32 位有符号整数范围内,初始推断类型为 integer;如果超出 32 位但在 64 位范围内,初始推断为 bigint;如果进一步超出,则推断为高精度任意精度类型 numeric。包含小数点或科学计数法指数的常量,其初始推断类型总是固定为 numeric。

数值常量被赋予的初始数据类型仅作为类型推断算法的起点。在大多数实际查询上下文中,系统会根据运算符或目标列的类型要求,自动将其隐式强制转换为最恰当的数据类型。如有需要,也可以通过显式类型转换强制指定具体类型。例如将数值强制解释为单精度浮点型 real(float4):

REAL '1.23'  -- 标准字符串风格
1.23::REAL   -- PostgreSQL 历史风格

4.1.2.7. 其他类型的常量 #

任意类型的常量均可通过以下三种通用语法形式之一进行声明:

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

字符串常量的文本内容将被直接传递给名为 type 的目标类型的底层文本输入转换函数,解析返回指定类型的常量对象。如果常量的目标类型在上下文中毫无歧义(例如直接赋值给具有明确类型的表字段),则可以省略显式类型转换,系统会自动完成隐式推断。

字符串常量本身可以采用常规单引号记法或美元符界定记法书写。

此外,还可以使用类似函数调用的语法指定类型转换:

typename ( 'string' )

但并非所有类型名都允许这样使用,详见 Section 4.2.9。

::、CAST() 与函数调用语法不仅适用于字面常量,还可用于任意复杂表达式的运行时动态类型转换,详见 Section 4.2.9。为避免语法歧义,type 'string' 语法仅能用于简单字面常量的类型声明,且该语法不支持数组类型;为数组声明常量类型时必须使用 :: 或 CAST()。

CAST() 语法完全符合 SQL 国际标准。type 'string' 语法是对标准的泛化扩展(SQL 标准仅针对少数预设类型支持此写法,而 PostgreSQL 允许所有类型使用)。双冒号 :: 则是 PostgreSQL 广为人知的传统经典语法。

4.1.3. 运算符 #

一个自定义或内置运算符的名称是由来自以下字符列表中的字符组成、长度不超过 NAMEDATALEN-1(默认 63 字节)的字符序列:


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

但在运算符命名上存在几项硬性约束:

  • 运算符名称中严禁包含 -- 或 /*,因为它们在词法分析阶段会被直接当作注释的起始标记。

  • 多字符构成的运算符名称不能以 + 或 - 结尾,除非该名称中同时包含下列字符之一:


    ~ ! @ # % ^ & | ` ?

    例如,@- 是合法的运算符名称,而 *- 则是不合法的。这一限制使 PostgreSQL 能够在无需强制要求记号之间插入空格的情况下,平滑解析符合 SQL 标准的查询语句。

在使用非标准自定义运算符时,通常需要在相邻运算符之间主动插入空格以消除歧义。例如,如果你定义了一个名为 @ 的一元前缀运算符,不能直接写为 X*@Y,而必须写为 X* @Y,以确保解析器将其识别为两个独立的运算符,而非单个复合运算符。

4.1.4. 特殊字符 #

部分非字母数字字符在 SQL 中具有特定语法含义,且不同于普通运算符。详细用法在各语法元素的专题章节中介绍,本节仅做存在性说明并概括其核心用途:

  • 美元符号($)后跟数字用于在函数体定义或预备语句中表示位置参数。在其他场景下,美元符可作为标识符的组成部分,或构成美元符界定字符串的前后缀。

  • 圆括号(())具有常规数学含义,用于将子表达式分组并强制指定计算优先级。在某些场景下,圆括号是特定 SQL 命令固定语法结构的组成部分。

  • 方括号([])用于选取数组中的元素或数组切片,详见 ???。

  • 逗号(,)在多种语法结构中用作列表元素之间的分隔符。

  • 分号(;)代表单条 SQL 命令的终结。除字符串常量或双引号标识符内部外,分号不能作为普通字符嵌入命令中。

  • 冒号(:)用于在数组中截取切片(见 ???)。在特定 SQL 扩展方言(如嵌入式 SQL)中,冒号用作主机变量名的前缀。

  • 星号(*)在选择列表中用于表示数据表的所有字段列或复合值的全量成员。当作为聚合函数的参数时,表示该聚合函数不需要任何显式计算参数(如 count(*))。

  • 句点(.)用于数值常量中的小数点,以及用作模式名、表名与列名之间的层级限定分隔符。

4.1.5. 注释 #

单行注释是以两个连续连字符(--)开头、延续至该行末尾的字符序列:

-- 这是标准的 SQL 单行注释

此外,还支持 C 语言风格的块注释(多行注释):

/* 多行注释
 * 支持嵌套:/* 嵌套的块注释 */
 */

注释以 /* 开始,并在遇到匹配的 */ 时结束。按照 SQL 国际标准规定(与传统 C 语言不同),SQL 块注释支持多层嵌套,这极大方便了临时注释掉本身就含有块注释的大段 SQL 代码。

注释在深入的语法语义分析之前就会被从输入流中剔除,等价于被空白字符替换。

4.1.6. 运算符优先级 #

Table 4.2 展示了 PostgreSQL 中各运算符的计算优先级与结合性。大多数运算符具有相同优先级并且遵循左结合规则。运算符的优先级与结合性是硬编码在语法解析器内部的。如果你希望包含多个运算符的复杂表达式按照非默认顺序执行,请显式使用圆括号包裹。

Table 4.2. 运算符优先级(由高至低)

运算符 / 语法元素结合性说明
.左结合表名 / 列名层级限定分隔符
::左结合PostgreSQL 风格类型转换
[ ]左结合数组元素与下标选取
+ -右结合一元正号、一元负号
COLLATE左结合排序规则指定
AT左结合AT TIME ZONE, AT LOCAL
^左结合指数幂运算
* / %左结合乘法、除法、取模
+ -左结合加法、减法
(所有其他运算符)左结合所有其他原生内置与用户自定义运算符
BETWEEN IN LIKE ILIKE SIMILAR 范围包含、集合从属、字符串模式匹配
< > = <= >= <> 大小比较运算符
IS ISNULL NOTNULL IS TRUE, IS FALSE, IS NULL, IS DISTINCT FROM 等
NOT右结合逻辑非
AND左结合逻辑与
OR左结合逻辑或

需要特别注意的是,上述运算符优先级规则同样适用于与内置运算符同名的用户自定义运算符。例如,如果你为某种自定义类型定义了一个名为 “+” 的运算符,无论它的具体计算逻辑是什么,它都将继承与内置 “+” 完全相同的优先级和结合性。

当使用带模式前缀的限定名调用 OPERATOR 语法时,例如:

SELECT 3 OPERATOR(pg_catalog.+) 4;

该 OPERATOR 结构被统一赋予 Table 4.2 中标注为“所有其他运算符”的默认优先级,无论括号内具体封装的是哪个特定运算符。

Note

PostgreSQL 9.5 之前的版本在运算符优先级细节上略有不同。尤其是 <=、>= 与 <> 曾被视为通用运算符;IS 测试具有更高优先级;而 NOT BETWEEN 存在优先级歧义。调整这些规则是为了更好地契合 SQL 标准并消除等价结构中的混淆。在绝大多数日常场景下,这些规则演进不会改变既有查询的实际结果。