4.1. 词法结构#
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
标记出现在特定位置,而INSERT
的这种特定变体也需要VALUES
才能完成。每个命令的精确语法规则在第 VI 部分中进行了描述。
4.1.1. 标识符和关键字#
上面示例中的SELECT
、UPDATE
或VALUES
等标记是关键字的示例,即在 SQL 语言中具有固定含义的单词。标记MY_TABLE
和A
是标识符的示例。它们标识表、列或其他数据库对象的名称,具体取决于它们所使用的命令。因此,它们有时简单地称为“名称”。关键字和标识符具有相同的词法结构,这意味着在不知道语言的情况下无法知道标记是标识符还是关键字。可以在附录 C中找到关键字的完整列表。
SQL 标识符和关键字必须以字母(a
-z
,但也包括带变音符号的字母和非拉丁字母)或下划线 (_
) 开头。标识符或关键字中的后续字符可以是字母、下划线、数字 (0
-9
) 或美元符号 ($
)。请注意,根据 SQL 标准,标识符中不允许使用美元符号,因此使用美元符号可能会降低应用程序的可移植性。SQL 标准不会定义包含数字或以下划线开头或结尾的关键字,因此这种形式的标识符不会与标准的未来扩展发生冲突。
系统使用不超过NAMEDATALEN
-1 字节的标识符;可以在命令中编写更长的名称,但它们会被截断。默认情况下,NAMEDATALEN
为 64,因此最大标识符长度为 63 字节。如果此限制有问题,可以通过更改src/include/pg_config_manual.h
中的NAMEDATALEN
常量来提高限制。
关键字和未加引号的标识符不区分大小写。因此
UPDATE MY_TABLE SET A = 5;
可以等效地写为
uPDaTE my_TabLE SeT a = 5;
经常使用的一种约定是用大写字母编写关键字,用小写字母编写名称,例如
UPDATE my_table SET a = 5;
还有第二种标识符:定界标识符或带引号的标识符。它是通过将任意字符序列用双引号 ("
) 括起来形成的。定界标识符始终是标识符,而不是关键字。因此"select"
可用于引用名为“select”的列或表,而未加引号的select
将被视为关键字,因此在预期表或列名称的位置使用它时会引发解析错误。该示例可以用带引号的标识符这样编写
UPDATE "my_table" SET "a" = 5;
引号标识符可以包含任何字符,除了代码为零的字符。(要包含双引号,请写两个双引号。)这允许构造原本不可能的表或列名,例如包含空格或和号的表或列名。长度限制仍然适用。
引用标识符还会使其区分大小写,而未引用的名称始终折叠为小写。例如,标识符FOO
、foo
和"foo"
被PostgreSQL视为相同,但"Foo"
和"FOO"
与这三个标识符和彼此不同。(PostgreSQL中未引用的名称折叠为小写与 SQL 标准不兼容,该标准规定未引用的名称应折叠为大写。因此,根据该标准,foo
应等效于"FOO"
,而不是"foo"
。如果您想编写可移植的应用程序,建议您始终引用特定名称或从不引用它。)
引号标识符的一个变体允许包含由其代码点标识的转义 Unicode 字符。此变体以U&
(大写或小写 U 后跟和号)开头,紧接在左双引号之前,中间没有任何空格,例如U&"foo"
。(请注意,这会与运算符&
产生歧义。在运算符周围使用空格以避免此问题。)在引号内,Unicode 字符可以通过编写反斜杠后跟四位十六进制代码点编号或反斜杠后跟加号后跟六位十六进制代码点编号来指定转义形式。例如,标识符"data"
可以写成
U&"d\0061t\+000061"
以下不太简单的示例用西里尔字母书写俄语单词“slon”(大象)
U&"\0441\043B\043E\043D"
如果需要反斜杠以外的其他转义字符,可以使用字符串后的UESCAPE
子句指定,例如
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'
。请注意,这不是双引号字符 ("
)。
仅用空格(至少有一个换行符)分隔的两个字符串常量会连接在一起,并有效地处理为字符串已写为一个常量。例如
SELECT 'foo'
'bar';
等效于
SELECT 'foobar';
但是
SELECT 'foo' 'bar';
不是有效的语法。(这种有点奇怪的行为是由SQL指定的;PostgreSQL遵循该标准。)
4.1.2.2. 带有 C 样式转义的字符串常量#
PostgreSQL还接受“转义”字符串常量,这是对 SQL 标准的扩展。通过在开头的单引号前书写字母E
(大写或小写)来指定转义字符串常量,例如E'foo'
。(在跨行继续转义字符串常量时,仅在第一个开头的引号前书写E
。)在转义字符串中,反斜杠字符 (\
) 开始一个类似 C 的反斜杠转义序列,其中反斜杠和后续字符的组合表示一个特殊字节值,如表 4.1所示。
表 4.1 反斜杠转义序列
反斜杠转义序列 | 解释 |
---|---|
\b | 退格 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 制表符 |
\ , \ , \ (o = 0–7) | 八进制字节值 |
\x , \x (h = 0–9, A–F) | 十六进制字节值 |
\u , \U (x = 0–9, A–F) | 16 或 32 位十六进制 Unicode 字符值 |
反斜杠后面的任何其他字符都按原义解释。因此,要包含反斜杠字符,请写两个反斜杠 (\\
)。此外,除了正常的''
方式外,还可以通过编写\'
在转义字符串中包含一个单引号。
您有责任创建的字节序列,尤其是在使用八进制或十六进制转义时,在服务器字符集编码中组成有效的字符。一个有用的替代方法是使用 Unicode 转义或替代 Unicode 转义语法,如第 4.1.2.3 节中所述;然后服务器将检查字符转换是否可能。
注意
如果配置参数standard_conforming_strings为off
,那么PostgreSQL会在常规和转义字符串常量中识别反斜杠转义字符。但是,从PostgreSQL9.1 开始,默认值为on
,这意味着只在转义字符串常量中识别反斜杠转义字符。此行为更符合标准,但可能会破坏依赖于反斜杠转义字符始终被识别的历史行为的应用程序。作为一种解决方法,您可以将此参数设置为off
,但最好逐渐停止使用反斜杠转义字符。如果您需要使用反斜杠转义字符来表示特殊字符,请使用E
编写字符串常量。
除了standard_conforming_strings
,配置参数escape_string_warning和backslash_quote控制字符串常量中反斜杠的处理方式。
代码为零的字符不能出现在字符串常量中。
4.1.2.3. 带有 Unicode 转义字符的字符串常量#
PostgreSQL还支持另一种字符串转义语法,允许通过代码点指定任意 Unicode 字符。Unicode 转义字符串常量以U&
(大写或小写字母 U 后跟和号)开头,位于左引号之前,中间没有空格,例如U&'foo'
。(请注意,这会与运算符&
产生歧义。在运算符周围使用空格以避免此问题。)在引号内,可以通过在反斜杠后跟四位十六进制代码点编号或在反斜杠后跟加号后跟六位十六进制代码点编号来以转义形式指定 Unicode 字符。例如,字符串'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 代码点将转换为实际的服务器编码;如果不可能,则会报告错误。
此外,只有在打开配置参数standard_conforming_strings时,字符串常量的 Unicode 转义语法才有效。这是因为否则此语法可能会混淆解析 SQL 语句的客户端,从而可能导致 SQL 注入和类似的安全问题。如果将参数设置为关闭,则此语法将被拒绝并显示错误消息。
4.1.2.4. 美元引用的字符串常量#
虽然用于指定字符串常量的标准语法通常很方便,但当所需的字符串包含许多单引号时,可能难以理解,因为每个单引号都必须加倍。为了在这种情况允许更具可读性的查询,PostgreSQL提供了另一种称为“美元引用”的方式来编写字符串常量。美元引用的字符串常量由一个美元符号 ($
)、一个可选的零个或多个字符的“标签”、另一个美元符号、构成字符串内容的任意字符序列、一个美元符号、与开始此美元引用的标签相同的标签和一个美元符号组成。例如,以下是使用美元引用指定字符串“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\\]
,当函数体由PostgreSQL执行时,它将被识别。但由于序列与外部美元引用定界符$function$
不匹配,因此它只是常量中的一些字符,就外部字符串而言。
美元引用的字符串的标签(如果有)遵循与未引用的标识符相同的规则,但不能包含美元符号。标签区分大小写,因此$tag$String content$tag$
是正确的,但$TAG$String content$tag$
是不正确的。
紧跟关键字或标识符的美元引用的字符串必须用空格与之分隔;否则,美元引用定界符将被视为前一个标识符的一部分。
美元引用不是 SQL 标准的一部分,但它通常是编写复杂字符串文字比符合标准的单引号语法更方便的方法。它在过程函数定义中经常需要表示其他常量内的字符串常量时特别有用。使用单引号语法,上述示例中的每个反斜杠都必须写成四个反斜杠,这将在解析原始字符串常量时减少为两个反斜杠,然后在函数执行期间重新解析内部字符串常量时减少为一个反斜杠。
4.1.2.5. 位字符串常量#
位字符串常量看起来像常规字符串常量,在开头的引号之前立即有一个B
(大写或小写),例如,B'1001'
。位字符串常量中允许的唯一字符是0
和1
。
或者,可以使用前导X
(大写或小写)以十六进制表示法指定位字符串常量,例如,X'1FF'
。此表示法等效于一个位字符串常量,每个十六进制数字有四个二进制数字。
两种形式的位字符串常量都可以像常规字符串常量一样跨行继续。美元引用不能用于位字符串常量。
4.1.2.6. 数字常量#
数字常量以以下通用形式接受
digits
digits.[digits][e[+-]digits]
[digits].digits[e[+-]digits]
digitse[+-]digits
其中*digits
*是一个或多个十进制数字(0 到 9)。如果使用小数点,则小数点前后至少必须有一个数字。如果存在指数标记 (e
),则指数标记后面至少必须有一个数字。常量中不能嵌入任何空格或其他字符,但可以使用下划线,如下所述进行可视分组。请注意,任何前导加号或减号实际上都不被视为常量的一部分;它是应用于常量的运算符。
以下是一些有效的数字常量的示例
42
3.5
.001
5e2
1.925e-3
此外,这些形式的非十进制整数常量也是可以接受的
0xhexdigits
0ooctdigits
0bbindigits
其中*hexdigits
是一个或多个十六进制数字 (0-9, A-F),octdigits
是一个或多个八进制数字 (0-7),bindigits
*是一个或多个二进制数字 (0 或 1)。十六进制数字和基数前缀可以是大写或小写。请注意,只有整数可以采用非十进制形式,而小数部分的数字不能。
以下是一些有效的非十进制整数常量的示例
0b100101
0B10011001
0o273
0O755
0x42f
0XFFFF
为了便于视觉分组,可以在数字之间插入下划线。这些不会对常量的值产生进一步的影响。例如
1_500_000_000
0b10001000_00000000
0o_1_755
0xFFFF_FFFF
1.618_034
下划线不能出现在数字常量或数字组的开头或结尾(即小数点或指数标记之前或之后),并且一行中不允许出现多个下划线。
如果数字常量不包含小数点或指数,则最初假定其类型为integer
,如果其值适合integer
类型(32 位);否则,如果其值适合bigint
类型(64 位),则假定其类型为bigint
;否则,则将其视为numeric
类型。包含小数点和/或指数的常量最初总是假定为numeric
类型。
数字常量最初分配的数据类型只是类型解析算法的起点。在大多数情况下,常量将根据上下文自动强制转换为最合适的类型。在必要时,可以通过强制转换来强制将数字值解释为特定数据类型。例如,可以通过编写以下内容来强制将数字值视为real
(float4
) 类型
REAL '1.23' -- string style
1.23::REAL -- PostgreSQL (historical) style
实际上,这些只是下面讨论的常规转换符号的特殊情况。
4.1.2.7. 其他类型的常量#
可以使用以下符号中的任何一种输入任意类型的常量
type 'string'
'string'::type
CAST ( 'string' AS type )
字符串常量的文本传递给名为*type
*的类型的输入转换例程。结果是指定类型的常量。如果常量的类型没有歧义(例如,当它直接分配给表列时),则可以省略显式类型转换,在这种情况下,它会自动强制转换。
可以使用常规 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) 个字符组成的序列
- / < > = ~ ! @ # % ^ & | ` ?
但是,运算符名称有一些限制
--
和/*
不能出现在运算符名称的任何位置,因为它们将被视为注释的开头。多字符运算符名称不能以
+
或-
结尾,除非名称还包含至少一个这些字符
~ ! @ # % ^ & | ` ?例如,
@-
是一个允许的运算符名称,但*-
不是。此限制允许PostgreSQL解析符合 SQL 的查询,而无需在标记之间添加空格。
在使用非 SQL 标准运算符名称时,通常需要用空格分隔相邻运算符以避免歧义。例如,如果您定义了一个名为@
的前缀运算符,您不能编写X*@Y
;您必须编写X* @Y
以确保PostgreSQL将其读作两个运算符名称,而不是一个。
4.1.4. 特殊字符#
某些非字母数字字符具有特殊含义,与运算符不同。可以在描述相应语法元素的位置找到有关用法的详细信息。本节仅用于告知这些字符的存在并总结其用途。
美元符号 (
$
) 后跟数字用于表示函数定义或预处理语句正文中的位置参数。在其他情况下,美元符号可以是标识符或美元引号字符串常量的一部分。圆括号 (
()
) 具有其通常的含义,用于对表达式进行分组并强制执行优先级。在某些情况下,圆括号是特定 SQL 命令固定语法的一部分。方括号 (
[]
) 用于选择数组的元素。有关数组的更多信息,请参见 第 8.15 节。逗号 (
,
) 在某些语法结构中用于分隔列表的元素。分号 (
;
) 终止 SQL 命令。它不能出现在命令中的任何位置,除非在字符串常量或引号标识符中。冒号 (
:
) 用于从数组中选择 “切片”。(请参见 第 8.15 节。)在某些 SQL 方言(例如嵌入式 SQL)中,冒号用于为变量名称加前缀。星号 (
*
) 在某些情况下用于表示表行或复合值的所有字段。当用作聚合函数的参数时,它也具有特殊含义,即聚合不需要任何显式参数。数字常量中使用句点 (
.
),并用于分隔架构、表和列名称。
4.1.5. 注释#
注释是以双破折号开头的字符序列,并一直延伸到行尾,例如:
-- This is a standard SQL comment
或者,可以使用 C 样式块注释
/* multiline comment
* with nesting: /* nested block comment */
*/
其中注释以/*
开头,并一直延伸到匹配的*/
。这些块注释是嵌套的,如 SQL 标准中所指定,但与 C 不同,因此可以注释掉可能包含现有块注释的更大代码块。
在进一步进行语法分析之前,注释将从输入流中删除,并有效地替换为空格。
4.1.6. 运算符优先级#
表 4.2显示了PostgreSQL中运算符的优先级和结合性。大多数运算符具有相同的优先级,并且是左结合的。运算符的优先级和结合性已固化到解析器中。如果您希望具有多个运算符的表达式以优先级规则暗示的其他方式进行解析,请添加括号。
表 4.2. 运算符优先级(从最高到最低)
运算符/元素 | 结合性 | 描述 |
---|---|---|
. | 左 | 表/列名称分隔符 |
:: | 左 | PostgreSQL 样式类型转换 |
[ ] | 左 | 数组元素选择 |
+ - | 右 | 一元加号、一元减号 |
COLLATE | 左 | 校对选择 |
AT | 左 | AT TIME ZONE |
^ | 左 | 指数 |
* / % | 左 | 乘法、除法、模运算 |
+ - | 左 | 加法、减法 |
(任何其他运算符) | 左 | 所有其他本机和用户定义的运算符 |
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
构造中,将采用表 4.2中针对“任何其他运算符”显示的默认优先级。无论OPERATOR()
中出现哪个特定运算符,这一点都是正确的。
注意
PostgreSQL9.5 之前的版本使用略有不同的运算符优先级规则。特别是,<=``>=
和<>
过去被视为通用运算符;IS
测试过去具有更高的优先级;NOT BETWEEN
和相关构造的行为不一致,在某些情况下被视为具有NOT
而不是BETWEEN
的优先级。这些规则已更改,以更好地符合 SQL 标准,并减少对逻辑等效构造的不一致处理造成的混淆。在大多数情况下,这些更改不会导致行为更改,或者可能导致可以通过添加括号来解决的“没有这样的运算符”失败。但是,在某些情况下,查询可能会更改行为,而不会报告任何解析错误。