SQL 输入由一系列命令组成。一条命令由一系列记号构成,用一个分号(";")结尾。输入流的终止也结束一条命令。哪些记号是合法的取决于特定命令的语法。
记号可以是一个关键字、标识符、引号包围的标识符、文本(或常量)、特殊的字符符号。记号通常由空白分隔(空格/tab/换行符),但如果不存在混淆的时候也可以不用(通常只是一个特殊字符与一些其它记号类型相连的时候)。
另外,在 SQL 输入里可以有注释。它们不是记号,它们实际上等效于空白。
比如,下列命令是(语法上)合法的 SQL 输入:
SELECT * FROM MY_TABLE; UPDATE MY_TABLE SET A = 5; INSERT INTO MY_TABLE VALUES (3, 'hi there');
这里是三条命令的序列,每条一行(尽管并不要求这么做;多条命令可以在一行里,单条命令也可以合理地分裂成多行)。
如果从哪些记号标识命令、哪些是操作数或参数的角度考虑,SQL 语法并不是非常一致。通常头几个记号是命令名字,因此上面的例子我们通常可以说是一个"SELECT"、一个"UPDATE"、和一个"INSERT"命令。不过,UPDATE 命令总是要求一个 SET 在某个位置出现,并且这个特定的 INSERT 还要求有一个 VALUES 才完整。每条命令的准确语法规则都在 Part VI 里描述。
像上面例子里的 SELECT, UPDATE, VALUES 这样的记号都是关键字的例子,也就是那些在 SQL 语言里有固定含义的单词。记号 MY_TABLE 和 A 是标识符的例子。根据使用它们的命令的不同,它们标识表、字段、或者其它数据库对象的名字。因此,有时候只是简单地叫它们"名字"。关键字和标识符有着同样的词法结构,意思是我们在没有认识这种语言之前是无法区分一个记号是标识符还是名字。你可以在附录C里找到一个关键字的完整列表。
SQL 标识符和关键字必须以一个字母(a-z 以及带变音符的字母和非拉丁字母)或下划线(_)开头,随后的字符可以是字母、下划线、数字(0-9)、美元符号($)。需要注意的是,根据 SQL 标准,美元符号不允许出现在标识符中,因此使用美元符号将不易移植。SQL 标准不会定义包含数字或者以下划线开头或结尾的关键字,因此按照这里的格式定义的标识符是安全的,不会和将来标准的扩展特性冲突。
系统使用不超过 NAMEDATALEN-1 个字符作为标识符;你可以在命令中写更长的名字,但它们会被截断。NAMEDATALEN 的缺省值是 64 ,因此标识符最大长度是 63 。如果觉得这个限制有问题,那么你可以在 src/include/postgres_ext.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;
引号包围的标识符可以包含编码不等于零的任意字符(要包含一个双引号,可以写两个相连的双引号)。这样我们就可以构造那些原本是不允许的表名或者字段名,比如那些包含空白或与号(&)的名字。但长度限制依旧。
把一个标识符用引号包围起来同时也令它大小写相关,而没有引号包围起来的名字总是转成小写。比如,我们认为标识符 FOO, foo, "foo" 是等价的 PostgreSQL 名字,但 "Foo" 和 "FOO" 与上面三个以及它们之间都是不同的。PostgreSQL 里对未加引号的名子总是转换成小写,这和 SQL 标准是不兼容的,SQL 标准要求未用引号包围起来的名字总是转成大写。因此根据标准,foo 等于 "FOO" 但不等于 "foo" 。如果你想编写可移植的程序,那么我们建议你要么就总是用引号包围某个名字,要么就从来不引。
在 PostgreSQL 里有三种隐含类型的常量:字符串、位串、数值。常量也可以声明为明确的类型,这样就可以使用更准确的表现形式以及可以通过系统更有效地处理。这些将在后面的小节描述。
SQL 里的一个字符串文本是用单引号(')包围的任意字符序列,比如 'This is a string' 。这种声明字符串常量的方法是 SQL 标准定义的。在这种类型的字符串常量里嵌入单引号的标准兼容的做法是敲入两个连续的单引号,比如 'Dianne''s horse' 。注意:两个连续的单引号不是双引号(")。
两个只是通过至少一个换行符的空白分隔的字符串常量会被连接在一起,并当做它们是写成一个常量处理。比如:
SELECT 'foo' 'bar';
等效于
SELECT 'foobar';
但
SELECT 'foo' 'bar';
是非法的语法。这个怪异的行为是 SQL 声明的,PostgreSQL 遵循标准。
PostgreSQL 还允许 "逃逸"字符串中的内容,这是一个 PostgreSQL 对 SQL 标准的扩展。逃逸字符串语法是通过在字符串前写字母 E(大写或者小写)的方法声明的。比如 E'foo' 。当需要续行包含逃逸字符的字符串时,仅需要在第一行的开始引号前写上 E 就可以了。逃逸字符串使用的是C-风格的反斜杠(\)逃逸:\b(退格)、\f(进纸)、\n(换行)、\r(回车)、\t(水平制表符)。此外还支持 \digits 格式的逃逸字符(这里的 digits 是一个八进制字节数值),以及 \xhexdigits 格式的逃逸字符(这里的hexdigits 代表十六进制字节值)。你创建的字节序列是否是服务器的字符集编码能接受的正确字符,是你自己的责任。任何其它跟在反斜杠后面的字符都当做文本看待。因此,要在字符串常量里包含反斜杠,则写两个反斜杠(\\)。另外,PostgreSQL 允许用一个反斜杠来逃逸单引号(\'),不过,将来版本的 PostgreSQL 将不允许这么用。所以最好坚持使用符合标准的 '' 。
警告 |
如果配置参数 standard_conforming_strings 的值是 off ,那么 PostgreSQL 将能够识别所有(无论有无前导 E)字符串常量中的反斜杠逃逸,这是为了与过去的历史行为兼容。虽然 standard_conforming_strings 目前的默认值是 off ,但是在不久的将来会变成 on 以与标准兼容。我们鼓励在应用中不使用反斜杠逃逸。如果你确实需要使用反斜杠逃逸来表示特殊字符,那么请在字符串常量前加上 E 以确保能够被正确的处理。 除 standard_conforming_strings 之外,escape_string_warning 和 backslash_quote 配置参数也影响字符串常量中反斜杠的处理。 |
编码为零的字符不允许出现在字符串常量中。
尽管声明字符串常量的标准方法通常都很方便,但是如果字符串中包含很多单引号或者反斜杠,那么理解字符串的内容可能就会变得很苦涩,因为每个单引号都要加倍。为了让这种场合下的查询更具可读性,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$ 则是错误的。
一个后面紧跟着关键字或者标识符的美元符界定字符串必须用空白与其后的关键字或者标识符隔开;否则美元符界定符将会被当作标识符的开头部分([原文] otherwise the dollar quoting delimiter would be taken as part of the preceding identifier)。
美元符界定不是 SQL 标准,但是在写复杂的字符串文本的时候,它通常比标准的单引号语法更方便。尤其是在其它常量里表现字符串常量的时候更有用。比如在过程函数定义里,如果用单引号语法,每个上面例子里的每个反斜杠都必须写四个,它们在作为字符串文本分析的时候会减少为两个,然后在函数执行的时候在内层字符串常量里会再次被解析为一个。
位串常量看起来很像在开引号前面有一个 B(大写或小写)的普通字符串(它们之间没有空白),比如 B'1001' 。位串常量里可以用的字符只有 0 和 1 。
另外,位串常量可以用十六进制表示法声明,方法是使用前缀 X(大写或者小写),比如 X'1FF' ,其中的每个十六进制位等效于四个二进制位。
两种形式的位串常量都可以像普通字符串常量那样跨行连续。位串常量不能用美元符界定。
数值常量接受下列通用的形式:
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(历史的)风格
这些实际上只是下面讨论的通用转换的特例。
任意类型的常量都可以用下列表示法中的任何一种来输入:
type 'string' 'string'::type CAST ( 'string' AS type )
其中的 'string' 将会被转换为 type 类型的常量。如果不存在该常量所属类型的歧义,那么可以省略明确的类型转换(比如,当你把它直接赋予一个表字段的时候),这种情况下它会自动转换。
其中的 'string' 可以用普通 SQL 表示法或者美元符界定来书写。
我们还可以用函数风格的语法来声明类型转换:
typename ( 'string' )
不过并非所有类型名都可以这样使用;参阅节4.2.8获取细节。
::, CAST() 和函数调用语法也可以用于声明任意表达式的运行时类型转换(如节4.2.8中讨论的那样)。但是 type 'string' 的形式只能用于声明一个字面常量的类型。type 'string' 的另外一个限制是它不能用于数组类型(要用 :: 或 CAST() 声明一个数组常量的类型)。
CAST() 语法遵循 SQL 标准。type 'string' 语法是标准的一个推广:SQL 只是给少数几种数据类型声明了这个语法,但 PostgreSQL 允许将其用于所有类型。:: 和函数调用的语法是 PostgreSQL 的历史用法。
一个操作符是最多 NAMEDATALEN-1 个(缺省63个)下列字符的序列:
+ - * / < > = ~ ! @ # % ^ & | ` ?
不过,有几个限制:-- 和 /* 不能出现在操作符中的任何地方,因为它们会被当做注释开始对待。
多字符操作符不能以 + 或 - 结束,除非其中至少还包含下列操作符之一:
~ ! @ # % ^ & | ` ?
比如,@- 是允许的操作符,但 *- 不是。这个限制允许 PostgreSQL 在不要求记号之间有空白的情况下分析 SQL 兼容的查询。当你使用非 SQL 标准的操作符的时候,你通常需要用空白分隔相邻的操作符以避免歧义。比如,如果你定义了一个叫 @ 的左单目操作符,那么你就不能写成 X*@Y ;而是要写成 X* @Y 以确保 PostgreSQL 把它读成两个操作符,而不是一个。
有些非字母数字字符有一些特殊含义,因此不能用做操作符。它们的用法细节可以在相应的描述语法元素的地方找到。本节只是描述它们的存在和概括一下这些字符的目的。
美元符号($)后面跟着数字用于在一个函数体定义或者预备语句中表示参数的位置。在其它环境里美元符号可能是一个标识符名字或者是一个美元符界定的字符串常量的一部分。
圆括弧(())用于分组和强制优先级的时候含义与平常一样。有些场合里圆括弧是作为一个特定 SQL 命令的固定语法的一部分要求的。
方括弧([])用于选取数组元素。参阅节8.10获取更多信息。
逗号(,)在一些语法构造里用于分隔一个列表的元素。
分号(;)结束一条 SQL 命令。它不能出现在一条命令里的任何地方,除了在引号包围的字符串常量或者标识符中。
冒号(:)用于从数组中选取"片段"(参阅节8.10)。在一些 SQL 方言里(比如嵌入 SQL),冒号用于前缀变量名。
星号(*)在某些环境里表示一个表的全部字段或者一个复合类型的值。在用作聚集函数的参数时还表示该聚集并不需要明确的参数。
句点(.)用在数字常量里,并用于分隔模式、表、字段名。
注释是任意以双划线开头并延伸到行尾的任意字符序列,比如:
-- 这是标准的 SQL92 注释
另外,还可以使用C-风格的块注释:
/* 多行注释 * 可以嵌套: /* 被嵌套的块注释 */ */
这里注释以 /* 开头并扩展到对应的 */ 。这些块注释可以嵌套,就像 SQL99 里说的那样(但和 C 不一样),因此我们可以注释掉一大块已经包含块注释的代码。
注释在进一步的语法分析之前被从输入中流删除并用空白代替。
表4-1显示了 PostgreSQL 里面的操作符的优先级和关联性。大多数操作符都有相同的优先级并且都是左关联的。这种情况可能会有不那么直观的行为;比如,布尔操作符 < 和 > 与布尔操作符 <= 和 >= 之间有着不同的优先级。同样,当你把双目和单目操作符组合使用的时候,有时候也需要加圆括弧。比如
SELECT 5 ! - 6;
会被分析成
SELECT 5 ! (- 6);
因为分析器不知道 ! 被定义成了后缀操作符,而不是中缀操作符(知道的时候只能是太晚了)。要在本例中获得你需要的特性,你要写成
SELECT (5 !) - 6;
这是我们为扩展性付出的代价。
表4-1. 操作符优先级(递减)
操作符/元素 | 关联性 | 描述 |
---|---|---|
. | 左 | 表/字段名分隔符 |
:: | 左 | PostgreSQL 特有的类型转换操作符 |
[ ] | 左 | 数组元素选择 |
- | 右 | 单目负号 |
^ | 左 | 幂 |
* / % | 左 | 乘,除,模 |
+ - | 左 | 加,减 |
IS | IS TRUE, IS FALSE, IS UNKNOWN, IS NULL | |
ISNULL | 测试是否为 NULL | |
NOTNULL | 测试是否不为 NULL | |
(任何其它的) | 左 | 所有其它的本地和用户定义操作符 |
IN | 集合成员 | |
BETWEEN | 范围包含 | |
OVERLAPS | 时间间隔重叠 | |
LIKE ILIKE SIMILAR | 字符串模式匹配 | |
< > | 小于,大于 | |
= | 右 | 等于,赋值 |
NOT | 右 | 逻辑非 |
AND | 左 | 逻辑与 |
OR | 左 | 逻辑或 |
请注意操作符优先级也适用于和上面提到的同名的内置操作符和用户定义操作符。比如,如果你为一些客户数据类型定义一个"+"操作符,那么它和内置的"+"操作符有同样的优先级,不管用它来干什么。
如果在 OPERATOR 语法里使用了模式修饰的操作符名,比如
SELECT 3 OPERATOR(pg_catalog.+) 4;
那么 OPERATOR 构造就会有表4-1里面为"任何其它"操作符显示的缺省优先级。不管什么特定的操作符出现在 OPERATOR() 里都是这样。