本章描述 SQL 的语法。它是理解后续各章的基础,后续各章将详细介绍如何使用 SQL 命令定义和修改数据。
我们也建议已经熟悉 SQL 的用户仔细阅读本章,因为其中包含一些在各 SQL 数据库之间实现并不一致的规则和概念,以及一些 PostgreSQL 特有的规则和概念。
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 才算完整。每个命令的精确语法规则见 Part VI。
上例中的 SELECT、UPDATE 或 VALUES 这类词元都是关键词,也就是在 SQL 语言中具有固定含义的词。词元 MY_TABLE 和 A 则是标识符的例子。它们标识表、列或其他数据库对象的名称,这取决于它们所在的命令。因此,它们有时也简称为“名称”。关键词和标识符具有相同的词法结构,这意味着如果不了解该语言,就无法判断一个词元究竟是标识符还是关键词。完整的关键词列表可以在Appendix 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"。(注意这会与操作符 & 产生歧义。请在该操作符两侧加空格以避免这个问题。)在引号内,可以通过写一个反斜线后跟 4 位十六进制代码点编号,或者一个反斜线后跟加号再跟 6 位十六进制代码点编号,来指定 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 代码点会被转换为实际的服务器编码;如果无法转换,则会报告错误。
在PostgreSQL中有三种隐式类型常量:字符串、位串和数字。也可以为常量指定显式类型,这样系统就能更准确地表示并更高效地处理它们。这些方式将在后续小节中讨论。
SQL 中的字符串常量是由单引号括起的任意字符序列,例如 'This is a string'。要在字符串常量中包含单引号字符,请写两个相邻的单引号,例如 'Dianne''s horse'。注意,这与双引号字符(")不同。
两个只由空白及至少一个新行分隔的字符串常量会被连接在一起,并且将作为一个写在一起的字符串常量来对待。例如:
SELECT 'foo' 'bar';
等同于:
SELECT 'foobar';
但是:
SELECT 'foo' 'bar';
则不是合法的语法(这种有些奇怪的行为是SQL指定的,PostgreSQL遵循了该标准)。
PostgreSQL也接受“转义”字符串常量,这也是SQL标准的一个扩展。一个转义字符串常量可以通过在开单引号前面写一个字母E(大写或小写形式)来指定,例如E'foo'(当一个转义字符串常量跨行时,只在第一个开引号之前写E)。在一个转义字符串内部,一个反斜线字符(\)会开始一个 C 风格的反斜线转义序列,在其中反斜线和后续字符的组合表示一个特殊的字节值(如Table 4.1中所示)。
Table 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 转义语法,如Section 4.1.2.3中所述;这样服务器会检查字符转换是否可行。
如果配置参数standard_conforming_strings为off,那么PostgreSQL对常规字符串常量和转义字符串常量中的反斜线转义都识别。不过,从PostgreSQL 9.1 开始,该参数的默认值为on,意味着只在转义字符串常量中识别反斜线转义。这种行为更兼容标准,但是可能打断依赖于历史行为(反斜线转义总是会被识别)的应用。作为一种变通,你可以设置该参数为off,但是最好迁移到符合新的行为。如果你需要使用一个反斜线转义来表示一个特殊字符,为该字符串常量写上一个E。
在standard_conforming_strings之外,配置参数escape_string_warning和backslash_quote也决定了如何对待字符串常量中的反斜线。
编码为零的字符不能出现在字符串常量中。
PostgreSQL 还支持另一种字符串转义语法,它允许用代码点指定任意 Unicode 字符。Unicode 转义字符串常量在开引号前紧接着写 U&(大写或小写字母 U 后跟和号),中间没有任何空白,例如 U&'foo'。(注意这会与操作符 & 产生歧义。请在该操作符两侧加空格以避免这个问题。)在引号内,可以通过写一个反斜线后跟 4 位十六进制代码点编号,或者一个反斜线后跟加号再跟 6 位十六进制代码点编号,来指定 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 代码点会被转换为实际的服务器编码;如果无法转换,则会报告错误。
此外,字符串常量的 Unicode 转义语法只有在配置参数 standard_conforming_strings 开启时才有效。这是因为否则这种语法可能会混淆解析 SQL 语句的客户端,从而导致 SQL 注入以及类似的安全问题。如果该参数被设置为 off,这种语法将被拒绝并报错。
虽然指定字符串常量的标准语法通常很方便,但当目标字符串中包含很多单引号时,就会变得难以阅读,因为每个单引号都必须写成两个。为了在这种情况下让查询更易读, PostgreSQL 提供了另一种编写字符串常量的方式,称为“美元引用”(dollar quoting)。一个美元引用字符串常量由美元符号($)、一个由零个或多个字符组成的可选“标签”、另一个美元符号、组成字符串内容的任意字符序列、一个美元符号、开始该美元引用时使用的同一个标签以及最后一个美元符号组成。例如,下面是使用美元引用指定字符串 “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 标准的一部分,但在书写复杂字符串字面量时,它往往比符合标准的单引号语法更方便。当要表示的字符串常量位于其他常量内部时,它尤其有用,这种情况在过程函数定义中很常见。如果使用单引号语法,上一个示例中的每个反斜线都必须写成四个反斜线;在解析原始字符串常量时它们会缩减为两个反斜线,而在函数执行期间重新解析内层字符串常量时又会变成一个。
位串常量看起来像常规字符串常量在开引号之前(中间无空白)加了一个B(大写或小写形式),例如B'1001'。位串常量中允许的字符只有0和1。
或者,也可以用十六进制记法指定位串常量,即在开头写一个 X(大写或小写形式),例如 X'1FF'。这种记法等价于把每个十六进制位替换成四个二进制位得到的位串常量。
两种形式的位串常量可以以常规字符串常量相同的方式跨行继续。美元引用不能被用在位串常量中。
数字常量接受下列一般形式:
digitsdigits.[digits][e[+-]digits] [digits].digits[e[+-]digits]digitse[+-]digits
其中 digits 是一个或多个十进制数字(0 到 9)。 若使用小数点,则小数点前后至少有一侧必须有数字。若使用指数标记 (e),其后至少必须有一位数字。常量中不能嵌入空格或 其他字符,但可按下文所述使用下划线进行视觉分组。请注意,前导正号或负号 实际上不属于常量本身,而是作用于常量的操作符。
这些是合法数字常量的示例:
42
3.5
4.
.001
5e2
1.925e-3
此外,还支持以下形式的非十进制整型常量:
0xhexdigits0ooctdigits0bbindigits
其中 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(32 位),它首先被假定为类型integer。否则如果它的值适合类型bigint(64 位),它被假定为类型bigint。再否则它会被取做类型numeric。包含小数点和/或指数的常量总是首先被假定为类型numeric。
为数字常量最初指定的数据类型,只是类型解析算法的起点。在大多数情况下,常量会根据上下文被自动强制转换成最合适的类型。必要时,你可以通过类型转换强制把一个数值解释为特定的数据类型。 例如,可以通过下面的写法强制把一个数值当作类型 real(float4)处理:
REAL '1.23' -- string style 1.23::REAL -- PostgreSQL (historical) style
这些实际上只是下面将要讨论的一般类型转换记法的特例。
任意类型的常量都可以用下列记法之一输入:
type'string' 'string'::typeCAST ( 'string' AStype)
字符串常量的文本会被传递给名为 type 的类型的输入转换例程。其结果是一个指定类型的常量。如果该常量必须具有的类型不存在歧义(例如,它被直接赋给某个表列时),就可以省略显式类型转换,在这种情况下它会被自动强制转换。
字符串常量可以使用常规 SQL 记法或美元引用书写。
也可以用类似函数调用的语法来指定类型强制转换:
typename( 'string' )
但并非所有类型名都可以这样使用,详见Section 4.2.9。
如Section 4.2.9中所述,::、CAST() 以及函数调用语法也可以用来指定任意表达式的运行时类型转换。为避免语法歧义, 语法只能用来指定简单字面常量的类型。该语法的另一个限制是不能用于数组类型;要指定数组常量的类型,请使用 type 'string':: 或 CAST()。
CAST()语法符合 SQL。语法是该标准的一般化:SQL 指定这种语法只用于一些数据类型,但是PostgreSQL允许它用于所有类型。带有type 'string'::的语法是PostgreSQL的历史用法,就像函数调用语法一样。
操作符名称由下列列表中的字符组成,长度最多为 NAMEDATALEN-1 个字符(默认是 63):
+ - * / < > = ~ ! @ # % ^ & | ` ?
不过,操作符名称也有一些限制:
-- 和 /* 不能出现在操作符名称中的任何位置,因为它们会被视为注释的开始。
多字符操作符名称不能以 + 或 - 结尾,除非该名称中还至少包含下列字符中的一个:
~ ! @ # % ^ & | ` ?
例如,@- 是合法的操作符名称,但 *- 不是。这个限制使 PostgreSQL 能够在不要求词元之间必须有空格的情况下解析符合 SQL 的查询。
当使用非 SQL 标准的操作符名时,你通常需要用空格分隔相邻的操作符来避免歧义。例如,如果你定义了一个名为@的前缀操作符,你不能写X*@Y,你必须写X* @Y来确保PostgreSQL把它读作两个操作符名而不是一个。
某些非字母数字字符具有不同于操作符的特殊含义。关于它们的详细用法,可以在描述相应语法元素的位置找到。本节只是为了提示它们的存在,并概述这些字符的用途。
美元符号($)后跟数字时,用来表示函数定义体或预备语句中的位置参数。在其他上下文中,美元符号可以是标识符的一部分,也可以是美元引用字符串常量的一部分。
圆括号(())具有它们通常的含义,用来分组表达式并且强制优先。在某些情况中,圆括号被要求作为一个特定 SQL 命令的固定语法的一部分。
方括号([])被用来选择一个数组中的元素。更多关于数组的信息见Section 8.15。
逗号(,)被用在某些语法结构中来分割一个列表的元素。
分号(;)结束一个 SQL 命令。它不能出现在一个命令中间的任何位置,除了在一个字符串常量中或者一个被引用的标识符中。
冒号(:)被用来从数组中选择“切片”(见Section 8.15)。在某些 SQL 的“方言”(例如嵌入式 SQL)中,冒号被用来作为变量名的前缀。
星号(*)在某些上下文中用来表示表行或组合值的所有字段。当它被用作聚合函数的参数时,还有一种特殊含义,即该聚合不需要任何显式参数。
句点(.)被用在数字常量中,并且被用来分割模式、表和列名。
注释是一串以双连字符开始并延伸到行尾的字符,例如:
-- This is a standard SQL comment
另外,也可以使用 C 风格注释块:
/* multiline comment * with nesting: /* nested block comment */ */
这里该注释开始于/*并且延伸到匹配出现的*/。这些注释块可按照 SQL 标准中指定的方式嵌套,但和 C 中不同。这样我们可以注释掉一大段可能包含注释块的代码。
在进一步进行语法分析之前,注释会从输入流中移除,并实际被替换为空白。
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 中所示的“任意其他操作符”。无论 OPERATOR() 中指定的是哪个具体操作符,这一点都成立。
版本 9.5 之前的PostgreSQL使用的操作符优先级 规则略有不同。特别是,<=、>= 和<>习惯于被当作普通操作符,IS 测试习惯于具有较高的优先级。并且在一些认为NOT比 BETWEEN优先级高的情况下,NOT BETWEEN 和相关的结构的行为不一致。为了更好地兼容 SQL 标准并且减少对 逻辑上等价的结构不一致的处理,这些规则也得到了修改。在大部分情况下, 这些变化不会导致行为上的变化,或者可能会产生“no such operator” 错误,但可以通过增加圆括号解决。不过在一些极端情况中,查询可能在 没有被报告解析错误的情况下发生行为的改变。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。