4.1. 词法结构

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

记号可以是一个key word, 一个 identifier, quoted identifier, literal(或常量), 或特殊的字符符号。 记号通常由空白分隔(空格/tab/换行符),但如果不存在混淆的时候也 可以不用(通常只是一个特殊字符与一些其它记号类型相连的时候)。

比如,下列命令是(语法上)合法的SQL输入:

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

这里是三条命令的序列,每条一行(尽管并不要求这么做;多条命令可以在 一行里,单条命令也可以合理地分裂成多行)。

另外,comments可以出现在SQL输入中,它们不是标记,但实际上它们等效于空白。

如果从哪些记号标识命令、哪些是操作数或参数的角度考虑,SQL 语法并 不是非常一致。通常头几个记号是命令名子,因此上面的例子我们通常可以 说是一个"SELECT",一个"UPDATE",和一个"INSERT"命令。不过,UPDATE命令总是 要求一个SET在某个位置出现,并且这个特定的INSERT还要求有一个VALUES才完整。 每条命令的准确语法规则都在Part VI里描述。

4.1.1. 标识符和关键子

像上面例子里的SELECT,UPDATEVALUES这样的记号都是关键子的例子,也就是那些 在SQL语言里有固定含义的单词。记号MY_TABLEA是标识符的例子。根据使用它们的命令的不同, 它们标识表、子段、或者其它数据库对象的名子。因此,有时候只是 简单地叫它们"names"。关键子和标识符有着同样的 词法结构,意思是我们在没有认识这种语言之前是无法区分一个 记号是标识符还是名子。你可以在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;

还有第二种标识符:delimited identifierquoted identifier。它是通过在"中包围 任意字符序列形成的。分隔标识符总是一个标识符,而不是关键子。因此, 你可以用"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内的(最多\u007F)代码点可以被声明。 4位和8位的数字形式可以被用来将UTF-16代理对声明为大于U+FFFF的带有代码点的字符, 尽管这样做是不必的(通过可用8位数字形式技术)。 (当服务器字符集是UTF8时使用代理对,首先,它们结合成一个单一的代码点,然后再UTF-8编码)

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

4.1.2. 常量

PostgreSQL里有三种implicitly-typed constants:字符串、位串、数值。常量也可以声明为明确的类型, 这样就可以使用更准确的表现形式以及可以通过系统更有效地处理。 这些将在后面的小节描述。

4.1.2.1. 字符串常量

SQL里的一个字符串文本是用单引号(')包围的任意字符序列, 比如'This is a string'。这种声明字符串常量的 方法是SQL标准定义的。在这种类型的字符串常量里嵌入单引号的标准兼容的 做法是敲入两个连续的单引号,比如'Dianne''s horse'。 注意:两个连续的单引号not是双引号 (")

两个只是通过with at least one newline的空白分隔 的字符串常量会被连接在一起,并当做它们是写成一个常量处理。比如:

SELECT 'foo'
'bar';

等效于

SELECT 'foobar';

但是

SELECT 'foo'      'bar';

是非法的语法。这个怪异的行为是SQL声明的, PostgreSQL遵循标准。

4.1.2.2. C结构的逃逸字符串常量

PostgreSQL还允许"escape"字符串中 的内容,这是一个PostgreSQL对SQL标准的扩展。逃逸字符串语法是通过 在字符串前写字母E(大写或者小写)的方法声明的。 比如 E'foo'。当需要续行包含逃逸字符的字符串时,仅需要在第一行的 开始引号前写上E就可以了。 在逃逸字符串中,通过一个\开始C-结构的反斜杠逃逸序列, 在该逃逸中,反斜杠与其之后字符的组合代表一个特殊的子节值,可参阅Table 4-1

Table 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逃逸语法(参阅Section 4.1.2.3)。 (后者通过写出子节来处理UTF-8字符集,这样做是很繁琐的)。

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

Caution

如果配置参数 standard_conforming_strings 的值是 off,那么PostgreSQL 将能够识别所有(无论有无前导 E)字符串常量中的反斜杠逃逸,这是为了 与过去的历史行为兼容。虽然standard_conforming_strings 目前的默认值是off,但是在不久的将来会变成on 以与标准兼容。我们鼓励在应用中不使用反斜杠逃逸。如果你确实需要使用 反斜杠逃逸来表示特殊字符,那么请在字符串常量前加上E 以确保能够被正确的处理。

standard_conforming_strings之外, escape_string_warningbackslash_quote 置参数也影响字符串常量中反斜杠的处理。

编码为零的字符不允许出现在字符串常量中。

4.1.2.3. 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内的(最多\u007F)代码点可以被声明。 4位和8位的数字形式可以被用来将UTF-16代理对声明为大于U+FFFF的带有代码点的字符, 尽管这样做是不必的(通过可用8位数字形式技术)。 (当服务器字符集是UTF8时使用代理对,首先,它们结合成一个单一的代码点,然后再UTF-8编码)

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

字面上,为了将逃逸字符写到标示符中,可以将它写两次。

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

尽管声明字符串常量的标准方法通常都很方便,但是如果字符串中包含 很多单引号或者反斜杠,那么理解字符串的内容可能就会变得很苦涩, 因为每个单引号都要加倍。为了让这种场合下的查询更具可读性, PostgreSQL允许另外一种称作"美元符界定"的 字符串常量书写办法。一个通过美元符界定声明的字符串常量由一个美元 符号($)、零个或多个字符组成的"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\\] ,在函数体被 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. 其它类型的常量

arbitrary类型的常量都可以用下列表示法中的 任何一种来输入:

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

其中的'string'将会被转换为type类型的常量。 如果不存在该常量所属类型的歧义,那么可以省略明确的 类型转换(比如,当你把它直接赋予一个表子段的时候),这种情况下它 会自动转换。

其中的'string'可以用普通SQL表示法或者美元符界定来书写。

我们还可以用函数风格的语法来声明类型转换:

typename ( 'string' )

不过并非所有类型名都可以这样使用;参阅Section 4.2.9获取细节。

::, CAST()和函数调用语法 也可以用于声明任意表达式的运行时类型转换(如节Section 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. 注释

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

-- This is a standard SQL comment

另外,还可以使用C-结构的块注释:

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

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

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

4.1.6. 词法优先级

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

SELECT 5 ! - 6;

会被分析成

SELECT 5 ! (- 6);

因为分析器不知道!被定义成了后缀操作符,而不是中缀操作符 (知道的时候只能是太晚了)。要在本例中获得你需要的特性,你要写成 SELECT (5 !) - 6; 这是我们为扩展性付出的代价。

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

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

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

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

SELECT 3 OPERATOR(pg_catalog.+) 4;

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