本文描述了
SQL
的语法,构成了理解后续具体介绍如何使用
SQL
定义和修改数据的基础。
即使已熟悉
SQL
,也请仔细阅读本文,因为其中部分概念和规则,可能同
SQL
数据库中实现得不一 致,或者属于 KingbaseES
中特有的规则和概念。
1
词法结构
SQL
输入由一个命令序列组成。一个命令由一个记号的序列构成,并由一个分号(“
;
”)终结。输入流的末端也会标志一个命令的结束。具体哪些记号是合法的,与具体命令的语法有关。
一个记号可以是关键词、标识符、带引号的标识符、
literal
(或常量)或者一个特殊字符符号。记号通常以空 白(空格(KingbaseES
同时支持编码为
UTF
、
GBK
和
GB18030
的中文或英文空格)、制表符、新行)来分隔, 但当无歧义时,不强制要求如此(唯一的例子是特殊字符紧挨着其他记号)。
例如,下面是一个(语法上)合法的
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
来完成命令。每个命令的精确语法
1.1
标识符和关键词
上例中的
SELECT
、
UPDATE
或
VALUES
记号是关键词的例子。关键词即
SQL
语言中具有特定意义的词。
记号
MY_TABLE
和
A
则是标识符的例子。标识表、列或者其他数据库对象的名字,取决于使用它们的命令。
因此有时也被简称为“名字”。关键词和标识符具有相同的词法结构。关键词的完整列表可以在
SQL
关键词中找到。
SQL
标识符和关键词必须以一个字母(
a
-
z
,也可以是带变音符的字母和非拉丁字母)或一个下划线(
_
)开始。后续字符可以是字母、下划线(_
)、数字(
0
-
9
)或美元符号(
$
)。注意根据
SQL
标准的字母规定,美 元符号是不允许出现在标识符中的,因此它们的使用可能会降低应用的可移植性。SQL
标准不会定义包含数字或者以下划线开头或结尾的关键词,因此这种形式的标识符不会与未来可能的标准扩展冲突。
系统中一个标识符的长度不能超过
NAMEDATALEN
-1
字节,在命令中可以写超过此长度的标识符,但是它们会被截断。默认情况下,NAMEDATALEN
的值为
64
,因此标识符的长度上限为
63
字节。
关键词和不被引号修饰的标识符大小写不敏感。因此:
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
;
受限标识符可以包含任何字符,除了代码为
0
的字符(如果要包含一个双引号,则写两个双引号)。这使得 可以构建原本不被允许的表或列的名称,例如包含空格或花号的名字。但是长度限制依然有效。
一种受限标识符的变体允许包括转义的用代码点标识的
Unicode
字符。这种变体以
U&
(大写或小写
U
跟上
一个花号)开始,后面紧跟双引号修饰的名称,两者之间没有任何空白,如
U&"foo"
(注意这里与操作符
&
似乎有一些混淆,但是在
&
操作符周围使用空白避免了这个问题)。在引号内,
Unicode
字符可以以转义
的形式指定:反斜线接上
4
位
16
进制代码点号码或者反斜线和加号接上
6
位
16
进制代码点号码。例如,标
识符
"data"
可以写成:
U
&
"d
\006
1t\+000061"
下面的例子用斯拉夫语字母写出了俄语单词“
slon
”(大象):
U
&
"
\044
1
\043
B
\043
E
\043
D"
如果希望使用其他转义字符来代替反斜线,可以在字符串后使用
UESCAPE
子句,例如:
U
&
"d!0061t!+000061"
UESCAPE
'!'
转义字符可以是除了
16
进制位、加号、单引号、双引号、空白字符之外的任意单个字符。注意转义字符是
被写在单引号而不是双引号内。
在标识符中包括转义字符本身,将其写两次即可。
Unicode
转义语法只有在服务器编码为
UTF8
时才起效。当使用其他服务器编码时,只有在
ASCII
范围内(最高到\007F
)的编码点才能被使用。
4
位和
6
位形式都可以被用来定义
UTF-16
代理对来组成代码点大于U+FFFF 的字符,尽管
6
位形式的存在使得这种做法变得不必要(代理对并不被直接存储,而是被被绑定到一个单独的代码点然后被编码到 UTF-8
)。
将一个标识符变得受限的同时,也将使它变成大小写敏感(在
KingbaseES
中若受限名称全是大写将转为小写存储,全部小写或者大小写混合的受限名称将按照用户输入的格式进行存储),反之非受限名称总是被转换成小写形式。例如,标识符 FOO
、
foo
和
"foo"
在
KingbaseES
中被认为是相同的,而
"Foo"
和
"FOO"
则互不相同且也不同于前面三个标识符(KingbaseES
将非受限名字转换为小写形式与
SQL
标准是不兼容的,
SQL标准中要求将非受限名称转换为大写形式。这样根据标准,foo
应该和
"FOO"
而不是
"foo"
相同。如果希望写一个可移植的应用,我们应该总是用引号修饰一个特定名字或者从不使用引号修饰)。
1.2
常量
在
KingbaseES
中有三种隐式类型常量:字符串、位串和数字。常量也可以被指定为显示类型,这可以使得它
被更精确地展示、更有效地处理。
1.2.1
字符串常量
在
SQL
中,一个字符串常量是一个由单引号(
'
)包围的任意字符序列,例如
'This is a string'
。为
了在一个字符串中包括一个单引号,可以写两个相连的单引号,例如
'Dianne''s horse'
。注意这和一个双引号("
)不同。
两个只由空白及至少一个新行分隔的字符串常量会被连接在一起,并且将作为一个写在一起的字符串常量来对待。例如:
SELECT
'foo'
'bar'
;
等同于:
SELECT
'foobar'
;
但是:
SELECT
'foo' 'bar'
;
不是合法的语法(此行为由
SQL
指定,
KingbaseES
遵循了该标准)。
1.2.2 C
风格转义的字符串常量
KingbaseES
也接受“转义”字符串常量,这也是
SQL
标准的一个扩展。一个转义字符串常量可以通过在开单引号前面写一个字母 E
(大写或小写形式)来指定,例如
E'foo'
(当一个转义字符串常量跨行时,只在第一个开引号之前写 E
)。在一个转义字符串内部,一个反斜线字符(
\
)会开始一个
C
风格的
反斜线转义序列,在其中反斜线和后续字符的组合表示一个特殊的字节值。
跟随在一个反斜线后面的任何其他字符被当做其字面意思。因此,要包括一个反斜线字符,请写两个反斜线 (\\
)。在一个转义字符串中包括一个单引号除了普通方法
''
之外,还可以写成
\'
。
你要负责保证你创建的字节序列由服务器字符集编码中合法的字符组成,特别是在使用八进制或十六进制转 义时。如果服务器编码为 UTF-8
,那么应该使用
Unicode
转义或替代的
Unicode
转义语法(在
带有
Unicode
转 义的字符串常量
中解释)。替代方案可能是手工写出
UTF-8
编码字节,这可能会非常麻烦。
只有当服务器编码是
UTF8
时,
Unicode
转义语法才能完全工作。当使用其他服务器编码时,只有在
ASCII 范围(低于\u007F
)内的代码点能够被指定。
4
位和
8
位形式都能被用来指定
UTF-16
代理对,用来组成 代码点超过 U+FFFF
的字符,不过
8
位形式的可用从技术上使得这种做法不再是必须的(当服务器编码为 UTF8 并使用代理对时,它们首先被结合到一个单一代码点,然后会被用
UTF-8
编码)。
Caution:
如果配置参数
standard_conforming_strings
为
off
,那么
KingbaseES
对常规字符串常量和转义 字符串常量中的反斜线转义都识别。不过,从 KingbaseES V8R2
开始,该参数的默认值为
on
,意味着只 在转义字符串常量中识别反斜线转义。这种行为更兼容标准,但是可能打断依赖于历史行为(反斜线转 义总是会被识别)的应用。作为一种变通,你可以设置该参数为 off
,但是最好迁移到符合新的行为。
如果你需要使用一个反斜线转义来表示一个特殊字符,为该字符串常量写上一个
E
。
在
standard_conforming_strings
之外,配置参数
escape_string_warning
和
backslash_quote
也决定 了如何对待字符串常量中的反斜线。
代码零的字符不能出现在一个字符串常量中。
1.2.3
带有
Unicode
转义的字符串常量
KingbaseES
也支持另一种类型的字符串转义语法,它允许用代码点指定任意
Unicode
字符。一个
Unicode
转 义字符串常量开始于 U&
(大写或小写形式的字母
U
,后跟花号),后面紧跟着开引号,之间没有任何空白,
例如
U&'foo'
(注意这产生了与操作符
&
的混淆。在操作符周围使用空白来避免这个问题)。在引号内, Unicode 字符可以通过写一个后跟
4
位十六进制代码点编号或者一个前面有加号的
6
位十六进制代码点编号 的反斜线来指定。例如,字符串'data'
可以被写为
U
&
'd
\006
1t\+000061'
下面的例子用斯拉夫字母写出了俄语的单词“
slon
”(大象):
U
&
'
\044
1
\043
B
\043
E
\043
D'
如果想要一个不是反斜线的转义字符,可以在字符串之后使用
UESCAPE
子句来指定,例如:
U
&
'd!0061t!+000061'
UESCAPE
'!'
转义字符可以是出一个十六进制位、加号、单引号、双引号或空白字符之外的任何单一字符。
只有当服务器编码是
UTF8
时,
Unicode
转义语法才能完全工作。当使用其他服务器编码时,只有在
ASCII 范围(低于\u007F
)内的代码点能够被指定。
4
位和
8
位形式都能被用来指定
UTF-16
代理对,用来组成 代码点超过 U+FFFF
的字符,不过
8
位形式的可用从技术上使得这种做法不再是必须的(当服务器编码为 UTF8 并使用代理对时,它们首先被结合到一个单一代码点,然后会被用
UTF-8
编码)。 还有,只有当配置参数 standard_conforming_strings
被打开时,用于字符串常量的
Unicode
转义语法才能工作。
这是因为否则这种语法将迷惑客户端中肯地解析
SQL
语句,进而会导致
SQL
注入以及类似的安全性问题。 如果这个参数被设置为关闭,这种语法将被拒绝并且报告一个错误消息。
要在一个字符串中包括一个表示其字面意思的转义字符,把它写两次。
1.2.4
美元引用的字符串常量
虽然用于指定字符串常量的标准语法通常都很方便,但是当字符串中包含了很多单引号或反斜线时很难理 解它,因为每一个都需要被双写。要在这种情形下允许可读性更好的查询,KingbaseES
提供了另一种被称为 “美元引用”的方式来书写字符串常量。一个美元引用的字符串常量由一个美元符号($
)、一个可选的零个
或更多字符的“标签”、另一个美元符号、一个构成字符串内容的任意字符序列、一个美元符号、开始这个美 元引用的相同标签和一个美元符号组成。例如,这里有两种不同的方法使用美元引用指定字符串“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\\]
,当该函数体被
King
baseES
执行时它将被识别。但是因为该序列不匹配外层的美元引用的定界符
$function$
,它只是一些在外层字符串所关注的常量中的字符而已。
一个美元引用字符串的标签(如果有)遵循一个未被引用标识符的相同规则,除了它不能包含一个美元符号之外。标签是大小写敏感的,因此 $tag$String content$tag$
是正确的,但是
$TAG$String
content$tag$
不正确。
一个跟着一个关键词或标识符的美元引用字符串必须用空白与之分隔开,否则美元引用定界符可能会被作为前面标识符的一部分。
美元引用不是
SQL
标准的一部分,但是在书写复杂字符串文字方面,它常常是一种比兼容标准的单引号语法更方便的方法。当要表示的字符串常量位于其他常量中时它特别有用,这种情况常常在过程函数定义中出现。如果用单引号语法,上一个例子中的每个反斜线将必须被写成四个反斜线,这在解析原始字符串常量时
会被缩减到两个反斜线,并且接着在函数执行期间重新解析内层字符串常量时变成一个。
1.2.5
位串常量
位串常量看起来像常规字符串常量在开引号之前(中间无空白)加了一个
B
(大写或小写形式),例如
B'1001'
。位串常量中允许的字符只有
0
和
1
。
作为一种选择,位串常量可以用十六进制记号法指定,使用一个前导
X
(大写或小写形式)
,
例如
X'1FF'
。
这种记号法等价于一个用四个二进制位取代每个十六进制位的位串常量。
两种形式的位串常量可以以常规字符串常量相同的方式跨行继续。美元引用不能被用在位串常量中。
2.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'
--
string style
1.23
::REAL
--
KingbaseES (historical) style
这些实际上只是接下来要讨论的一般造型记号的特例。
1.2.7
其他类型的常量
一种任意类型的一个常量可以使用下列记号中的任意一种输入:
type 'string'
'string'::type
CAST ( 'string' AS type )
字符串常量的文本被传递到名为
“type“
的类型的输入转换例程中。其结果是指定类型的一个常量。如果对该常量的类型没有歧义(例如,当它被直接指派给一个表列时),显式类型造型可以被忽略,在那种情况下它会被自动强制。
字符串常量可以使用常规
SQL
记号或美元引用书写。
也可以使用一个类似函数的语法来指定一个类型强制:
typename ( 'string' )
但是并非所有类型名都可以用在这种方法中,详见
类型转换
。
如
类型转换
中讨论的,
::
、
CAST()
以及函数调用语法也可以被用来指定任意表达式的运行时类型转换。要避免语法歧义,type
’
“string“
’
语法只能被用来指定简单文字常量的类型。
type
’
“string“
’
语法上的另一个限制是它无法对数组类型工作,指定一个数组常量的类型可使用::
或
CAST()
。
CAST()
语法符合
SQL
。
type
’
“string“
’
语法是该标准的一般化:
SQL
指定这种语法只用于一些数据类型,但是 KingbaseES
允许它用于所有类型。带有
::
的语法是
KingbaseES
的历史用法,就像函数调用语法一样。
1.3
操作符
一个操作符名是最多
NAMEDATALEN
-1
(默认为
63
)的一个字符序列,其中的字符来自下面的列表:
+?-?*?/?<?>?=?~?!?@?#?%?^?&?|¿??
不过,在操作符名上有一些限制:
•
--
and
/*
不能在一个操作符名的任何地方出现,因为它们将被作为一段注释的开始。
•
一个多字符操作符名不能以
+
或
-
结尾,除非该名称也至少包含这些字符中的一个:
~?!?@?#?%?^?&?|¿??
例如,
@-
是一个被允许的操作符名,但
*-
不是。这些限制允许
KingbaseES
解析
SQL
兼容的查询而不需要在记号之间有空格。
当使用非
SQL
标准的操作符名时,你通常需要用空格分隔相邻的操作符来避免歧义。例如,如果你定义了一个名为 @
的左一元操作符,你不能写
X*@Y
,你必须写
X* @Y
来确保
KingbaseES
把它读作两个操作符
名而不是一个。
1.4
特殊字符
一些不是数字字母的字符有一种不同于作为操作符的特殊含义。这些字符的详细用法可以在描述相应语法元素的地方找到。这一节只是为了告知它们的存在以及总结这些字符的目的。
•
跟随在一个美元符号(
$
)后面的数字被用来表示在一个函数定义或一个预备语句中的位置参数。在其他上下文中该美元符号可以作为一个标识符或者一个美元引用字符串常量的一部分。
•
圆括号(
()
)具有它们通常的含义,用来分组表达式并且强制优先。在某些情况中,圆括号被要求作为一个特定 SQL
命令的固定语法的一部分。
KingbaseES
同时支持编码为
UTF
、
GBK
和
GB18030
的中文、英文及中英文混合的圆括号。
•
方括号(
[]
)被用来选择一个数组中的元素。更多关于数组的信息见
数组
。
•
逗号(
,
)被用在某些语法结构中来分割一个列表的元素。
KingbaseES
同时支持编码为
UTF
、
GBK
和GB18030 的中文和英文的逗号输入。
•
分号(
;
)结束一个
SQL
命令。它不能出现在一个命令中间的任何位置,除了在一个字符串常量中或者一个被引用的标识符中。
•
冒号(
:
)被用来从数组中选择“切片”(见
数组
)。在某些
SQL
的“方言”(例如嵌入式
SQL
)中,冒号被用来作为变量名的前缀。
•
星号(
*
)被用在某些上下文中标记一个表的所有域或者组合值。当它被用作一个聚集函数的参数时,它还有一种特殊的含义,即该聚集不要求任何显式参数。
•
句点(
.
)被用在数字常量中,并且被用来分割模式、表和列名。
1.5
注释
一段注释是以双斜线开始并且延伸到行结尾的一个字符序列,例如:
--
This
is
a standard SQL comment
另外,也可以使用
C
风格注释块:
/*
multiline comment
*
with
nesting:
/*
nested block comment
*/
*/
这里该注释开始于
/*
并且延伸到匹配出现的
*/
。这些注释块可按照
SQL
标准中指定的方式嵌套,但和
C中不同。这样我们可以注释掉一大段可能包含注释块的代码。
在进一步的语法分析前,注释会被从输入流中被移除并且实际被替换为空白。
1.6
操作符优先级
表
1-2
显示了
KingbaseES
中操作符的优先级和结合性。大部分操作符具有相同的优先并且是左结合的。操作符的优先级和结合性被硬写在解析器中。
此外,当使用二元和一元操作符的组合时,有时你将需要增加圆括号。例如:
SELECT 5 ! - 6;
将被解析为:
SELECT 5 ! (- 6);
因为解析器不知道—知道时就为时已晚—
!
被定义为一个后缀操作符而不是一个中缀操作符。在这种情况下
要得到想要的行为,你必须写成:
SELECT (5 !) - 6;
只是为了扩展性必须付出的代价。
注意该操作符有限规则也适用于与上述内建操作符具有相同名称的用户定义的操作符。例如,如果你为某种自定义数据类型定义了一个“+
”操作符,它将具有和内建的“
+
”操作符相同的优先级,不管你的操作符要做什么。
当一个模式限定的操作符名被用在
OPERATOR
语法中时,如下面的例子:
SELECT
3
OPERATOR(sys_catalog
.+
)
4
;
OPERATOR
结构被用来为“任意其他操作符”获得表
1-2
中默认的优先级。不管出现在
OPERATOR()
中的是哪个指定操作符,这都是真的。
Note:
版本
V8R3
之前的
KingbaseES
使用的操作符优先级规则略有不同。特别是,
<=
、
>=
和
<>
习惯于被当作普通操作符,IS
测试习惯于具有较高的优先级。并且在一些认为
NOT
比
BETWEEN
优先级高的情况下,
NOT BETWEEN 和相关的结构的行为不一致。为了更好地兼容
SQL
标准并且减少对逻辑上等价的结构不一致的处理,这些规则也得到了修改。在大部分情况下,这些变化不会导致行为上的变化,或者可能会产生“no such operator”错误,但可以通过增加圆括号解决。不过在一些极端情况中,查询可能在没有被报告解析错误的情况下发生行为的改变。如果你发觉这些改变悄悄地破坏了一些事情,可以打开 operator_precedence_warning 配置参数,然后测试你的应用看看有没有一些警告被记录。