4.1. 词法结构

SQL 输入由一系列命令组成。一条命令由一系列记号 构成,用一个分号(";")结尾。输入流的终止也结束一条命令。 哪些记号是合法的取决于特定命令的语法。

记号可以是一个关键字标识符引号包围的标识符文本(或常量)、 特殊的字符符号。记号通常由空白分隔(空格/tab/换行符), 但如果不存在混淆的时候也可以不用(通常只是一个特殊字符与一些其它记号类型相连的时候)。

比如,下列命令是(语法上)合法的 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. 标识符和关键字

像上面例子里的SELECTUPDATEVALUES 这样的记号都是关键字的例子,也就是那些在 SQL 语言里有固定含义的单词。记号MY_TABLEA标识符的例子。根据使用它们的命令的不同, 它们标识表、字段、或者其它数据库对象的名字。因此,有时候只是简单地叫它们 "名字"。关键字和标识符有着同样的词法结构, 意思是我们在没有认识这种语言之前是无法区分一个记号是标识符还是名字。 你可以在附录 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 将被当做一条命令的一部分,因此如果把它当做一个表名或者字段名使用的话就会产生一个分析错误。 上面的例子可以用引号包围的标识符这么写:

UPDATE "my_table" SET "a" = 5;

引号包围的标识符可以包含编码不等于零的任意字符(要包含一个双引号,可以写两个相连的双引号)。 这样我们就可以构造那些原本是不允许的表名或者字段名,比如那些包含空白或与号(&)的名字。 但长度限制依旧。

一个带引号的标识符的变形允许带有代码点标记的转义Unicode字符。 该变形以U&开始(大/小写U后跟有&符号)紧跟着打开的双引号, 之间没有空格,例如U&"foo"。(需要注意的是, 这可能产生和操作符&之间的歧义。可以在操作符周围加上空格来避免该问题。) 在引号中,通过写一个后面跟有反斜杠和四位十六进制代码点或跟有反斜杠和加号和六位十六进制代码点, Unicode字符可以写成转义格式。 例如,"data"可以写成:

U&"d\0061t\+000061"

下例以西里尔字母写俄文"slon"(象)。

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

如果需要一个非反斜杠的不同的转义,可以通过在字符串之后使用UESCAPE 语句来进行声明,如:

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

转义字符可以是一个十六进制数字以外的任何单个字符,加号,一个单引号,一个双引号, 或一个空白字符。需要注意的是,转义字符是写在单引号中,而不是双引号中。

为了将转义字符写到标识符中,可以将它写两次。

只有服务器字符集是UTF8时,才会完全使用Unicode转义语法。 当使用其他服务器字符集时,只有在ASCII内的(最多\007F)代码点可以被声明。 4位和6位的数字形式可以被用来将UTF-16代理对声明为大于U+FFFF的带有代码点的字符, 尽管6位数字形式技术的可用性使得这样做没有必要。(代理对不是直接存储的, 首先,它们结合成一个单一的代码点,然后再用UTF-8编码。)

把一个标识符用引号包围起来同时也令它大小写相关,而没有引号包围起来的名字总是转成小写。 比如,我们认为标识符FOOfoo"foo" 是等价的PostgreSQL名字,但"Foo""FOO" 与上面三个以及它们之间都是不同的。PostgreSQL里对未加引号的名子总是转换成小写, 这和 SQL 标准是不兼容的,SQL 标准要求未用引号包围起来的名字总是转成大写。因此根据标准, foo等于"FOO"但不等于"foo" 。 如果你想编写可移植的程序,那么我们建议你要么就总是用引号包围某个名字,要么就从来不引。

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还允许"转义"字符串中的内容, 这是一个PostgreSQL对SQL标准的扩展。转义字符串语法是通过在字符串前写字母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字符值

任何其它跟在反斜杠后面的字符都当做文本看待。因此,要在字符串常量里包含反斜杠, 则写两个反斜杠(\\)。另外,PostgreSQL 允许用一个反斜杠来转义单引号\', 不过,将来版本 的 PostgreSQL 将不允许这么用。所以最好坚持使用符合标准的''

你有必要为你所创建的字节序列(特别是在使用八进制或十六进制转义时)编写有效的服务器字符集编码字符。 当服务器编码是UTF-8时,应该使用Unicode转义或另一种Unicode转义语法(参阅第 4.1.2.3 节)。 (后者通过写出字节来处理UTF-8字符集,这样做是很繁琐的)。

只有服务器字符集是UTF8时,才会完全使用Unicode转义语法。 当使用其他服务器字符集时,只有在ASCII内的(最多\u007F)代码点可以被声明。 4位和8位的数字形式可以被用来将UTF-16代理对声明为大于U+FFFF的带有代码点的字符, 尽管8位数字形式技术的可用性使得这样做没有必要。(当服务器编码是UTF8时使用代理对, 首先,它们结合成一个单一的代码点,然后再用UTF-8编码)

小心

如果配置参数standard_conforming_strings的值是off, 那么PostgreSQL将能够识别常规和转义字符串常量中的反斜杠转义。 然而,在PostgreSQL9.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'可以写成:

U&'d\0061t\+000061'

下例以西里尔字母写俄文"slon"(象)。

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

如果需要一个非反斜杠的不同的转义,可以通过在字符串之后使用 UESCAPE语句来进行声明,如:

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

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

只有服务器字符集是UTF8时,才会完全使用Unicode转义语法。 当使用其他服务器字符集时,只有在ASCII内的(最多\007F)代码点可以被声明。 4位和6位的数字形式可以被用来将UTF-16代理对声明为大于U+FFFF的带有代码点的字符, 尽管6位数字形式技术的可用性使得这样做没有必要。(当服务器编码是UTF8时使用代理对, 首先,它们结合成一个单一的代码点,然后再用UTF-8编码。)

同样,字符串常量的Unicode转义语法只有当配置参数standard_conforming_strings 启用时才能生效。否则,该语法在解析SQL语法时给客户端造成混淆,导致SQL注入或其他安全问题。 如果该参数设为OFF,该语法会带着一条错误消息一起被拒绝。

为了将转义字符写到字符串中,可以将它写两次。

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'。位串常量里可以用的字符只有01

另外,位串常量可以用十六进制表示法声明,方法是使用前缀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
4.
.001
5e2
1.925e-3

如果一个数值常量既不包含小数点,也不包含指数, 那么如果它的数值可以放在integer类型中(32位),则认为它是integer类型; 如果它的数值可以放在bigint中(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的输入转换过程。其结果是一个该类型的常量。 如果不存在该常量所属类型的歧义,那么可以省略明确的类型转换(比如,当你把它直接赋予一个表字段的时候), 这种情况下它会自动转换。

其中的字符串常量可以用普通 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个)下列字符的序列:

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

不过,有几个限制:

当你使用非 SQL 标准的操作符的时候,你通常需要用空白分隔相邻的操作符以避免歧义。 比如,如果你定义了一个叫@的左单目操作符,那么你就不能写成 X*@Y;而是要写成X* @Y以确保PostgreSQL 把它读成两个操作符,而不是一个。

4.1.4. 特殊字符

有些非字母数字字符有一些特殊含义,因此不能用做操作符。 它们的用法细节可以在相应的描述语法元素的地方找到。 本节只是描述它们的存在和概括一下这些字符的目的。

4.1.5. 注释

注释是任意以双划线开头并延伸到行尾的任意字符序列,比如:

-- 这是标准的 SQL 注释 

另外,还可以使用C风格的块注释:

/* 多行注释
 * 可以嵌套: /* 被嵌套的块注释 */
 */

这里注释以/*开头并扩展到对应的*/。 这些块注释可以嵌套,就像 SQL标准里说的那样(但和 C 不一样), 因此我们可以注释掉一大块已经包含块注释的代码。

注释在进一步的语法分析之前被从输入流中删除并用空白代替。

4.1.6. 操作符优先级

表 4-2显示了PostgreSQL 里面的操作符的优先级和关联性。大多数操作符都有相同的优先级并且都是左关联的。 操作符的优先级和关联性是硬连接到解析器的。这种情况可能会有不那么直观的行为; 比如,布尔操作符<>与布尔操作符<=>=之间有着不同的优先级。同样,当你把双目和单目操作符组合使用的时候, 有时候也需要加圆括弧。比如:

SELECT 5 ! - 6;

会被分析成:

SELECT 5 ! (- 6);

因为解析器不知道!被定义成了后缀操作符,而不是中缀操作符 (知道的时候只能是太晚了)。要在本例中获得你需要的特性,你要写成:

SELECT (5 !) - 6;

这是我们为扩展性付出的代价。

表 4-2. 操作符优先级(递减)

操作符/元素关联性描述
.表/字段名分隔符
::PostgreSQL特有的类型转换操作符
[ ]数组元素选择
+ -单目正号,单目负号
^
* / %乘,除,模
+ -加,减
IS IS TRUE, IS FALSE, IS NULL, etc
ISNULL 测试是否为NULL
NOTNULL 测试是否不为NULL
(任何其他的)所有其他的本地和用户定义操作符
IN 集合成员
BETWEEN 范围包含
OVERLAPS 时间间隔重叠
LIKE ILIKE SIMILAR 字符串模式匹配
< > 小于,大于
=等于,赋值
NOT逻辑非
AND逻辑与
OR逻辑或

请注意操作符优先级也适用于和上面提到的内置操作符同名的用户定义操作符。 比如,如果你为一些客户数据类型定义一个"+"操作符, 那么它和内置的"+"操作符有同样的优先级,不管用它来干什么。

如果在OPERATOR语法里使用了模式修饰的操作符名,比如:

SELECT 3 OPERATOR(pg_catalog.+) 4;

那么OPERATOR构造就会有表 4-2 里面为"任何其它的" 操作符显示的缺省优先级。不管什么特定的操作符出现在 OPERATOR()里都是这样。