1. SQL 简介
SQL 的目标
理想情况下,数据库语言应允许用户:
- 建立数据库和关系结构
- 完成基本数据管理任务,诸如关系中数据的插入、修改和删除。
- 完成简单或复杂的查询。
数据库语言必须功能丰富、结构简洁、易学易用。另外,语言必须易于移植,符合公认的标准,这样当我们更换到不同的 DBMS 时,仍可以使用相同的命令和语法结构。SQL 语言满足这些要求。
SQL 语言是 面向转换语言 的例子,它将输入关系转换为所需要的输出关系。作为语言,国际标准化组织(ISO)发布的 SQL 标准包括两个主要部分:
- 数据定义语言(Data Definition Language,DDL)用于定义数据库结构和数据的访问控制。
- 数据操作语言(Data Manipulation Language,DML)用于检索和更新数据。
SQL-1999 出现以前,SQL 仅包括数据定义和数据操作命令,不包括控制流命令,如 IF...THEN...ELSE、GO TO 或 DO...WHILE。这些命令的实现必须用编程语言或任务控制语言或由用户交互决定。由于缺乏计算完备性,仅能用以下两种方式使用 SQL。一种方法是在终端交互的输入 SQL 语句。另一种方法是将 SQL 语句嵌入过程化语言中。
SQL 是相对易学的语言:
- 非过程化语言:用户只需描述所需的信息,不需给出获取该信息的具体过程。换句话说 SQL 不需要指定数据的访问方法。
- SQL 和大多数现代语言一样,是无格式的,这意味着语句的每一部分不必固定在屏幕上的特定位置。
- SQL 命令由标准英语单词组成,如 CREATE TABLE、INSERT、SELECT 等。例如:
CRREATE TABLE Staff (staffNo VARCHAR(5), lName VARCHAR(15), salary DECIMAL(7, 2);
INSERT INTO Staff VALUES ('SG16', 'Brown', 8300);
SELECT staffNo, lName, salary FROM Staff WHERE salary > 10000;
- SQL 能被数据库管理员、管理人员、应用程序开发者和各类终端用户广泛使用。
目前 SQL 语言已有国际标准,它已成为定义和操作关系数据库名义上和事实上的标准(ISO,1992,2011a)。
SQL 的历史
(略)
SQL 的重要性
(略)
术语
ISO 组织公布的 SQL 标准并未使用正式术语,如关系,属性和元组,而是采用表、列和行这样的术语。通常使用 ISO 术语表示 SQL 语句。注意,SQL 并不严格支持前面给出的关系模型定义。例如,SQL 允许 SELECT 语句产生的结果表中包含重复行,它还强调列的顺序,并且允许用户对结果表中的行进行排序。
2. 书写 SQL 命令
SQL 语句包括 保留字 和 用户自定义字。保留字是 SQL 语言的固定部分,有固定的含义。保留字必须准确拼写,并且不能跨行拼写。用户自定义字由用户自己定义(根据一定的语法规则),用于表示表、列、视图和索引等数据库对象的名称。语句中的其他字也是根据一定的语法规则定义的。虽然标准并没有要求,但 SQL 的许多种实现版本都要求句子终结符来标识 SQL 语句的结束(通常用 “ ; ”)。
SQL 语句中的多数组成部分是 不区分大小写 的,即字母用大写或小写均可。唯一的例外是字符数据常量必须与其在数据库中已经存在的大小写形式一致。举例说明,若某人的姓用 “SMITH” 存储,而以字符串 “Smith” 查询,将查询不到结果。
SQL 语言格式虽然比较自由,但为了增加 SQL 语句和语句集的可读性,可采用缩进和下划线。例如:
- 语句中每一子句在新的一行开始书写。
- 每一子句与其他子句的开始字符处在同一列上。
- 如果子句由几个部分组成,则它们应当分别出现在不同的行,并在子句开始处使用缩进表明这种关系。
在下文中,将用扩展的巴克斯范式(Backus Naur Form,BNF)定义 SQL 语句:
- 大写字母用于表示保留字,必须准确拼写。
- 小写字母用于表示用户自定义字。
- 竖线(|)表示从选项中进行 选择,例如 a | b | c。
- 大括号表示 所需元素,例如 {a}。
- 中括号表示 可选元素,例如 [a]。
- 省略号(...)表示某一项 可选择重复 零到多次。
例如:{a | b} ( ,c...)
意思是 a 或 b 后紧跟着用逗号分开的零个或多个 c。
实际上,DDL 语句用于建立数据库结构(即表)和访问机制(即每个用户能合法访问什么),而 DML 语句用于查询和维护表。但这里先讲 DML 语句,这是为了说明 DML 语句对普通用户更重要。
3. 数据操作
SQL DML 语句有以下几种:
- SELECT:用于查询数据库中的数据。
- INSERT:用于将数据插入表中。
- UPDATE:用于更新表中的数据。
- DELETE:用于删除表中的数据。
由于 SELECT 语句比较复杂,而其他 DML 语句相对简单,这里将用大部分篇幅分析 SELECT 语句和它的各种形式,首先是简单的查询,随后增加排序、分组、聚集和涉及多个表的复杂查询。最后讨论 INSERT、UPDATE 和 DELETE 语句。
使用 DreamHome 数据库来说明 SQL 语句,包括下面各表:
relation | property |
---|---|
Branch | (<u>branchNo</u>, street, city, postcode) |
Staff | (<u>saffNo</u>, fName, lName, position, sex, DOB, salary, branchNo) |
PropertyForRent | (<u>propertyNo</u>, street, city, postcode, type, rooms, rent, ownerNo, staffNo, branchNo) |
Client | (<u>clientNo</u>, fName, lName, telNo, prefType, maxRent, eMail) |
PrivateOwner | (<u>ownerNo</u>, fName, lName, address, telNo, eMail, password) |
Viewing | (<u>clientNo</u>, <u>propertyNo</u>, viewDate, comment) |
常量
讨论 SQL DML 之前,很有必要理解常量的概念。常量是指 SQL 语句中使用的 不变量。不同的数据类型有不同的常量形式,简单来说,常量可以分为用引号引起来的和不用引号的。所有非数值型数据必须用单引号引起来,而所有数值型数据一定 不能 使用引号。例如,可以使用常量将数据插入表中:
INSERT INTO PropertyForRent(propertyNo, street, city, postcode, type, rooms, rent, ownerNo, staffNo, staffNo, branchNo)
VALUES ('PA14', '16 Holhead', 'Aberdeen', 'AB7 5SU', 'House', 6, 650.00, 'CO46', 'SA9', 'B007');
列 rooms 的值是整数,列 rent 的值是实数,它们不能用引号引起来,其他列的值均为字符串,必须使用引号。
简单查询
SELECT 语句用于检索并显示一个或多个数据库表中的数据。它功能强大,可以用一个语句完成关系代数中选择、连接和投影操作。SELECT 也是 SQL 命令中使用频率最高的语句,其形式如下:
SELECT [DISTINCT | ALL] {* | [columnExpression [AS newName]][,...]}
FROM TableName [alias][,...]
[WHERE condition]
[GROUP BY columnList][HAVING condition]
[ORDER BY columnList]
columnExpression 为一列名称或表达式,TableName 给出欲访问数据库中的表或视图的名称,alias 是可选用的 TableName 的简称。SELECT 语句处理过程顺序如下:
FROM 给出将用到的表
WHERE 过滤满足条件的行
GROUP BY 将具有相同属性值得行分成组
HAVING 过滤满足条件的组
SELECT 指定查询结果中出现的列
ORDER BY 指定查询结果的顺序
SELECT 语句中子句的顺序不能改变,仅有最开始的两个子句 SELECT 和 FROM 是必需的,其余子句均为可选择的。SELECT 操作为封闭的,即查询表的结果将用另一张表显示。该语句有多种变形,正像刚才解释的那样。
检索所有的行
例 6.1:检索所有的列和所有的行:
列举所有员工的情况。
因为本次查询无条件限制,所以省略 WHERE 子句,查询所有的列,语句如下:
SELECT staffNo, fName, lName, position, sex, DOB, salary, branchNo
FROM Staff;
因为 SQL 语句要检索所有的列,所以一个简便的表达方式是使用星号 “ * ” 代替 “所有列” 的名称。等效语句如下:
SELECT *
FROM Staff;
例 6.2:从所有行中检索指定的列:
生成所有员工的工资表,只包括员工编号、姓名及工资。
SELECT staffNo, fName, lName, salary
FROM Staff;
例 6.3:使用 DISTINCT:
列出被查看过的所有房产的编号。
SELECT propertyNo
FROM Viewing;
注意,因为同一房产可以被查看多次,所以结果中很可能会有重复值,SELECT 语句不会像关系代数的投影操作那样自动消除重复。用保留字 DISTINCT 可消除重复。下面是使用 DISTINCT 的版本:
SELECT DISTINCT propertyNo
FROM Viewing;
例 6.4:计算字段:
生成所有员工的月工资表,包括员工编号、姓名和工资。
SELECT staffNo, fName, lName, salary/12
FROM Staff;
给查询类似于例 6.2,不同之处在于需要查询的是每月的工资。查询结果可以通过简单的将年薪除以 12 得到。
本例是使用 计算字段(有时称为 导出字段)的例子。通常,使用计算字段时,在 SELECT 列表中给出 SQL 表达式,包括加、减、乘、除运算。另外,可以使用括号来建立复杂的表达式。表中可有多个使用计算字段的列。而且,算数表达式所引用的列必须是数字类型。
该查询结果表中第 4 列就是输出列 col4。通常结果表的列名应当和用于检索的数据库的列名相对应。但是本例这种情况下,SQL 并不知道如何标识列。惯用方法是根据列在表中的位置来命名(例如,col4)。在一些方言中,SELECT 列表中列名为空白或者用表达式写入。ISO 标准允许用 AS 子句为列命名。前面的例子可以用如下方式书写:
SELECT staffNo, fName, lName, salary/12 AS monthlySalary
FROM Staff;
这样,查询结果中的列将是 monthlySalary 而不是 col4。
行选择(WHERE 子句)
前面例子用 SELECT 语句检索表中所有的行。事实上,我们经常需要限制仅检索某些行,这时可用 WHERE 子句实现,包括关键字 WHERE 和其后给定的用于检索行的查询条件。五个基本的条件运算(ISO术语中的谓词)如下:
- 比较(comparison):比较两个表达式的值。
- 范围(range):测试表达式的值是否在指定的范围中。
- 成员关系(set membership):测试表达式的值是否在某一值集合内。
- 模式匹配(pattern match):测试字符串是否与指定模式相匹配。
- 空(null):测试列是否为空(未知)值。
WHERE 子句等价于前文讨论的关系代数的选择操作。现列出各种查找条件的例子。
例6.5 比较运算作为查找条件:
列出工资高于 10000 英镑的所有员工。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary > 10000;
其中,表是 Staff,谓词是 salary > 10000,查询结果产生一个工资高于 10000 英镑的所有员工的列表。
SQL 语句中可用的比较运算符如下:
符号 | 意义 |
---|---|
= | 等于 |
<> | 不等于(ISO标准) |
!= | 不等于(某些方言这样用) |
< | 小于 |
<= | 小于或等于 |
> | 大于 |
>= | 大于或等于 |
复杂的谓词可由逻辑运算符 AND、OR 和 NOT 产生,必要或期望时可用括号表示计算顺序。计值条件表达式的规则如下:
- 计值顺序由左至右。
- 首先计算括号中子表达式的值。
- NOT 优先于 AND 和 OR。
- AND 优先于 OR。
可运用括号消除歧义。
例 6.6 复合比较运算作为查找条件:
列出位于伦敦或格拉斯哥的所有分公司的地址。
SELECT *
FROM Branch
WHERE city = 'London' OR city = 'Glasgow';
逻辑运算符 OR 用于 WHERE 子句中,用于查找伦敦(city = 'London')或格拉斯哥(city = 'Glasgow')的分公司。
例 6.7 范围作为查找条件(BETWEEN / NOT BETWEEN):
列出工资在 20000 英镑和 30000 英镑之间的所有员工。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary BETWEEN 20000 AND 30000;
BETWEEN 测试包括范围的端点,所以查询结果包括工资为 20000 英镑和 30000 英镑的职工。BETWEEN 测试并不能增强 SQL 的功能,因为通过使用两个比较表达式也可以完成相同的功能。BETWEEN 只是简化了范围运算条件的表达。
例 6.8 集合成员测试作为查找条件(IN / NOT IN):
列出所有的经理和主管。
SELECT staffNo, fName, lName, position
FROM Staff
WHERE position IN ('Manager', 'Supervisor');
集合成员资格测试(IN)用于测试数据是否与值表中的某一值相匹配,本例中就是 “Manager” 或 “Supervisor”。
像 BETWEEN 一样,IN 测试并不能增强 SQL 的表达功能。上面的查询可以如下表达:
SELECT staffNo, fName, lName, position
FROM Staff
WHERE position = 'Manager' OR position = 'Supervisor';
但是,IN 测试提供了更加有效地查询条件表达方式,特别是集合中包括多个值的时候。
例 6.9 模式匹配作为查找条件(LIKE / NOT LIKE):
找出其地址中含有字符串 “Glasgow” 的所有业主。
该查询是从表 PrivateOwner 中查询地址中包括字符串 “Glasgow” 的行。SQL 有两种特殊的模式匹配符号:
- %:百分号表示零或多个字符序列(通配符)。
- _:下划线表示任意单个字符。
模式匹配中还有其他字符,例如:
-
address LIKE 'H%'
意味着字符串第一个字符必须是 H,对其他字符不做限制。 -
address LIKE 'H___'
意味着字符串正好有 4 个字符,第一个字符为 H。 -
address LIKE '%e'
意味着一个字符序列,长度最小为1,最后一个字符为 e。 -
address LIKE '%Glasgow%'
意味着一个包含字符串 Glasgow 的任意长度序列。 -
address NOT LIKE 'H%'
意味着字符串第一个字符不能为 H。
如果查找的字符串本身包含上述模式匹配字符,则可用转义字符。例如,匹配字符串 '15%' 用下列谓词:
LIKE '15#%' ESCAPE '#'
利用 SQL 的模式匹配,可以查询地址中包括字符串 “Glasgow” 的所有业主。查询语句如下:
SELECT ownerNo, fName, lName, address, telNo
FROM PrivateOwner
WHERE address LIKE '%Glasgow%';
注意:一些 RDBMS,如 Microsoft Office Access,使用通配符 * 和 ? 代替 % 和 _。
例 6.10 空查找条件(IS NULL / IS NOT NULL):
列出查看过房产编号为 PG4 的房产但没有留下评论的客户的情况。
对于没有评论的情况你可能会认为用下面这个表达式查询:
(propertyNo = 'PG4' AND comment = '')
然而,这种查询方式是无效的。空评论可以被认为是一个未知值,不能测试它是否等于另一个字符串。若用上面的复合条件,得到的结果将是一个空表。相反,用特定保留字 IS NULL 可显式地测试空值:
SELECT clientNo, viewDate
FROM Viewing
WHERE propertyNo = 'PG4' AND comment IS NOT NULL;
查询结果排序(ORDER BY 子句)
一般来说,SQL 查询结果中的各行不会自动以某种顺序来显示(虽然有一些 DBMS基于默认排序,例如基于主关键字)。这时,可以使用 ORDER BY 子句让查询结果按一定顺序显示。ORDER BY 子句包括所需排序的 列标识符 的列表,用逗号隔开。列标识符 可能是 列名字 或 列序号,列序号是指列在 SELECT 列表中的位置,“1” 标识列表中第一个(最左边)元素,“2”表示列表中第二个元素,以此类推。当被排序的列是表达式并且没有使用过 AS 子句赋予列将来可能引用的名字时,可以用列序号。ORDER BY 子句允许导出的行在任一列或多个列上按升序(ASC)或降序(DESC)排列,而不管列是否出现在查询结果中。然而一些实现版本要求 ORDER BY 子句中的元素必须出现在 SELECT 列表中。不论是哪一种情况,ORDER BY 子句都只能是 SELECT 语句的最后一个子句。
例 6.11 单列排序:
按工资降序的方式产生所有职工的工资列表。
SELECT staffNo, fName, lName, salary
FROM Staff
ORDER BY salary DESC;
本例类似于例 6.2,不同在于此处查询结果按工资降序排列。这主要由 SELECT 语句最后的 ORDER BY 子句完成,指定列 salary 用于排序,并使用 DESC 表示按降序排列。注意,还可以这样表达 ORDER BY 子句:ORDER BY 4 DESC,4 表示 SELECT 列表中第 4 列的名字,即 salary。
ORDER BY 子句可能包括多个元素,主排序关键字 决定查询结果总体的排序。例 6.11 中,主排序关键字是 salary。如果主关键字是唯一的,那么就没有必要引入第二个关键字来控制顺序。然而,如果主排序关键字的值不是唯一的,查询结果中就会有多个行对应主排序关键字的同一个值,这种情况下,可以增加一个排序关键字来控制主排序关键字相同的那些行的顺序。ORDER BY 子句中第二个元素也成为 次排序关键字。
例 6.12 多列排序:
产生按类型排序的一个房产简表。
SELECT propertyNo, type, rooms, rent
FROM PopertyForRent
ORDER BY type;
这时如果不指定次排序关键字,系统可以任何顺序对行进行排序。此时,可以再以租金为序组织房产,需指定次要顺序:
SELECT propertyNo, type, rooms, rent
FROM PropertyForRent
ORDER BY type, rent DESC;
现在,查询结果首先以房产类型升序排列(默认值为ASC),对于相同房产类型,以 rent 降序排列。
ISO标准指出,若 ORDER BY 子句中用于排序的列或表达式取空值,则即可以认为空值小于所有的非空值,也可以认为空值大于所有的非空值。这个选择权留给了 DBMS 的实现者。
使用 SQL 聚集函数
为了便于获取数据库中的行和列,我们通常希望对数据进行汇总或聚集操作,类似于报表底部的合计。ISO 标准定义了五个聚集函数:
- COUNT:返回指定列中数据的个数。
- SUM:返回指定列中数据的总和。
- AVG:返回指定列中数据的平均值。
- MIN:返回指定列中数据的最小值。
- MAX:返回指定列中数据的最大值。
这些函数只对表中的单个列进行操作,返回一个值。COUNT、MIN 和 MAX 可以用于数值和非数值字段,而 SUM 和 AVG 只能用于数值字段,除了 COUNT(*)外,每一个函数首先要去掉空值,然后计算其非空值。COUNT(*)是 COUNT 的特殊用法,计算表中所有行的数目,而不管是否有空值或重复出现。
若需要在应用函数之前消除重复,则必须在函数中的列名前使用关键字 DISTINCT。如果不需要去掉重复,ISO 标准允许使用关键字 ALL,虽然指定这个关键字事实上跟不指定没什么区别。DISTINCT 对 MIN 和 MAX 函数没有任何作用,而对 SUM 和 AVG 函数有效。所以计算时必须考虑重复项是否包含在计算中。另外,查询中 DISTINCT 只能指定一次。
注意聚集函数只能用于 SELECT 列表和 HAVING 子句中,用在其他地方都是不正确的。如果 SELECT 列表包括聚集函数,却没有使用 GROUP BY 子句分组,那么 SELECT 列表的任何项都不能引用列,除了作为聚集函数的参数。例如,下面的查询是非法的:
SELECT staffNo, COUNT(salary)
FROM Staff;
因为查询中没有 GROUP BY 子句,且 SELECT 列表中出现了列 staffNo,它并不是聚集函数的参数。
例 6.13 COUNT(*)的使用:
月租金超过 350 英镑的房产有多少处?
SELECT COUNT(*) AS myCount
FROM PropertyForRent
WHERE rent > 350;
用 WHERE 子句限制查询每月租金超过 350 英镑的房产,满足该条件的房产总数通过聚集函数 COUNT 得出。
例 6.14 COUNT(DISTINCT)的使用:
2013 年 5 月有多少处不同的房产被查看过?
SELECT COUNT(DISTINCT propertyNo) AS myCount
FROM Viewing
WHERE viewDate BETWEEN '1-May-13' AND '31-May-13';
用 WHERE 子句将查看房产的时间限定在 2013 年 5 月,满足条件的总数通过聚集函数 COUNT 得出。这样相同的房产被重复计数,必须用 DISTINCT 关键字去掉重复。
例 6.15 COUNT 和 SUM 的使用:
找出经理的总人数,并计算他们的工资总和。
SELECT COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
WHERE position = 'Manager';
用 WHERE 子句限制对经理进行查询。经理人数和工资总和分别通过将 COUNT 和 SUM 函数应用于受限集合而得出。
例 6.16 MIN、MAX 和 AVG 的使用:
找出所有员工工资的最小、最大和平均值。
SELECT MIN(salary) AS myMin, MAX(salary) AS myMax, AVG(salary) AS myAvg
FROM Staff;
这个例子是查找所有员工的工资情况,所以不需要 WHERE 子句。对于列 salary,利用 MIN、MAX 和 AVG 函数求出所需要的值。
查询结果分组(GROUP BY 子句)
上面汇总查询的结果相当于报表底部的合计值。报表中将查询结果用一个汇总行表示,而缩减了细节数据。通常报表中也需要有部分和,可用 GROUP BY 子句实现这种功能。包括 GROUP BY 子句的查询称为 分组查询,按 SELECT 列表中的列进行分组,每一组产生一个综合查询结果。GROUP BY 子句的列又称为 组列名。ISO 标准要求 SELECT 子句和 GROUP BY 子句紧密结合。当使用 GROUP BY 时,SELECT 列表中的项必须每组都有单一值。SELECT 子句仅可包括以下内容:
- 列名
- 聚集函数
- 常量
- 组合上述各项的表达式
SELECT 子句中的所有列除非用在聚集函数中,否则必须在 GROUP BY 子句中出现。反之,GROUP BY 子句中出现的列不一定出现在 SELECT 列表中。当 WHERE 子句和 GROUP BY 子句同时使用时,必须首先使用 WHERE 子句,分组由满足 WHERE 子句查询条件的那些行产生。
ISO 标准规定应用 GROUP BY 子句时,两个空值被认为是相等的。即如果两行在同一分组列上都为空值,并且在不含的分组列上值相等,则这两行被合并到同一组中。
例 6.17 GROUP BY 的使用:
找出工作在每一个分公司的员工人数和他们的工资总和。
SELECT branchNo, COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
GROUP BY branchNo
ORDER BY branchNo;
GROUP BY 列表中不必包括列名 staffNo 和 salary,因为其仅出现在 SELECT 列表的聚集函数中。另一方面,branchNo 没有出现在聚集函数中,所以必须出现在 GROUP BY 列表中。
理论上,SQL 按下列步骤完成查询:
- SQL 根据分公司编号将员工分成不同的组。每一组中,所有员工有相同的分公司编号。
- 每一组中,SQL 计算员工的人数,并计算出 salary 列的汇总以便得到员工薪水的总和。SQL 在查询结果中为每一组生成一个单独的汇总行。
- 最后,查询结果按分公司编号 branchNo 的升序排列。
SQL 标准允许 SELECT 列表包括某些嵌套查询,因而可用下面的语句表达上面的查询:
SELECT branchNo, (SELECT COUNT(staffNo) AS myCount
FROM Staff s
WHERE s.branchNo = b.branchNo),
(SELECT SUM(salary) AS mySum
FROM Staff s
WHERE s.branchNo = b.branchNo)
FROM Branch b
ORDER BY branchNo;
该例中,将为 Branch 列中的每个分公司产生两个聚集函数值,某些情况下聚集值可能为零。
分组约束(HAVING 子句)
HAVING 子句的设计意图食欲 GROUP BY 子句一起使用,来限定哪些 分组 将出现在最终查询结果中。虽然它与 WHERE 子句语法类似,但用途不同。WHERE 子句将单个行 “过滤” 到查询结果中,而 HAVING 子句则将 分组 “过滤” 到查询结果表中。ISO 标准要求 HAVING 子句使用的列名必须出现在 GROUP BY 子句列表中,或包括在聚集函数中。实际中,HAVING 子句的条件运算至少包括一个聚集函数,否则的话可把查询条件移到 WHERE 子句中来过滤单个行(记住聚集函数不能用在 WHERE 子句中)。
HAVING 子句并不是 SQL 的必要部分——任何使用 HAVING 子句的查询都可用不带 HAVING 子句的语句重写。
例 6.18 HAVING 的使用:
对于员工人数多于一人的分公司计算出每一个分公司的员工人数和他们的工资总和。
SELECT branchNo, COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
GROUP BY branchNo
HAVING COUNT(staffNo) > 1
ORDER BY branchNo;
此例类似于前面讲过的附加约束的例子,只找出员工人数超过一人的分组,可用 HAVING 子句对分组进行约束。
子查询
这里讨论将 SELECT 语句完全嵌套到另一个 SELECT 语句中的用法。内部 SELECT 语句(子查询)的结果用在外部语句中以决定最后的查询结果。子查询可以被使用在外部 SELECT 语句的 WHERE 和 HAVING 子句中,称为 子查询 或 嵌套查询。子查询也可以出现在 INSERT、UPDATE 和 DELETE 语句中。子查询有三种类型:
- 标量子查询:返回单个列和单个行,即单个值。原则上,标量子查询可用于任何需要单个值的地方。
- 行子查询:返回多个列,但只有单个行。行子查询可用于任何需要行值构造器的时候,如在谓词中。
- 表子查询:返回多个行,每行有一个或多个列。表子查询用于需要一个表的情况。例如,作为谓词 IN 的操作数。
例 6.19 用于相等判断的子查询:
列出在位于 “163 Main St” 的分公司中工作的员工的情况。
SELECT staffNo, fName, lName,position
FROM Staff
WHERE branchNo = (SELECT branchNo
FROM Branch
WHERE street = '163 Main St');
内部 SELECT 语句(SELECT branchNo FROM Branch...)找出位于 “163 Main St” 的分公司的编号(如果只有一个分公司编号,便是标量查询)。外部 SELECT 语句找出工作在此分公司的所有员工的情况。换句话说,内部查询返回一个与 “163 Main St” 对应的值 “B003”,外部查询语句变为:
SELECT staffNo, fName, lName, position
FROM Staff
WHERE branchNo = 'B003';
可以认为子查询产生一个临时表,便与外部语句访问和利用。在 WHERE 子句和 HAVING 子句中,子查询可以紧邻着关系运算符。子查询本身通常包括在圆括号中。
例 6.20 用于聚集函数的子查询:
列出个人工资高于平均工资的所有员工,并求出多余平均数的值。
SELECT staffNo, fName, lName, position, salary - (SELECT AVG(salary) FROM Staff) AS salDiff
FROM Staff
WHERE salary > (SELECT AVG(salary) FROM Staff);
首先注意,不能写 “WHERE salary > AVG(salary)”,因为聚集函数不能用于 WHERE 子句中。相反,先用子查询求出平均工资,然后使用外部 SELECT 语句找出那些工资高于平均数的员工。换句话说,子查询返回的是平均工资 17000 英镑。注意,在 SELECT 列表中使用了标量子查询,得以表示与平均工资的差。外查询可简写如下:
SELECT staffNo, fName, lName, position, salary - 17000 AS salDiff
FROM Staff
WHERE salary > 17000;
子查询应遵循如下规则:
- ORDER BY 子句不能用于子查询(虽然可用在最外面的 SELECT 语句中)。
- 子查询 SELECT 列表必须由单个列名或表达式组成,除非子查询使用了关键词 EXISTS。
- 默认的情况下,子查询中列名取自子查询的 FROM 子句中给出的表,也可通过限定列名的办法指定取自外查询的 FROM 子句中的表。
- 当子查询时比较表达式中的一个操作时,子查询必须出现在表达式的右面。
例 6.21 嵌套子查询,IN 的使用:
列出正由位于 “163 Main St” 的分公司的员工经营的房产。
SELECT propertyNo, street, city, postcode, type, rooms, rent
FROM PropertyForRent
WHERE staffNo IN (SELECT staffNo
FROM Staff
WHERE branchNo = (SELECT branchNo
FROM Branch
WHERE street = '163 Main St'));
最里面的查询,首先是查询位于 “163 Main St” 的分公司的编号,然后查询工作在这个分公司的员工。这时,可能会出现多个行,所以最外面的查询不能用等号(=),而是用 IN 关键字。最外层查询出中间层得到的员工管理的房产的情况。
ANY 和 ALL
关键字 ANY 和 ALL 用于产生单个列的子查询。若子查询前缀关键字 ALL,那么仅当子查询产生的所有值都满足条件时,条件才为真。若子查询前缀关键字 ANY,那么子查询产生的任何一个值(一个或多个)满足条件时,条件就为真。如果子查询是空值,ALL条件返回真值,ANY 条件返回假值。ISO 标准允许用限定词 SOME 代替 ANY。
例 6.22 ANY / SOME 的使用:
列出工资高于分公司 B003 中至少一位员工的工资的所有员工。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary > SOME(SELECT salary
FROM Staff
WHERE branchNo = 'B003');
这个查询中,首先用子查询找出工作在分公司 B003 的员工的最低工资,然后,外查询找出工资高于这个数值的所有员工。另一种方法是使用关键字 SOME / ANY。内查询产生集合,外查询找出工资高于集合中任一个数值的员工。这种方法看起来比找出子查询中最低工资更自然一些。
例 6.23 ALL 的使用:
列出工资高于分公司 B003 中任何员工的工资的所有员工。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary > ALL(SELECT salary
FROM Staff
WHERE branchNo = 'B003');
这个例子和上一个例子非常相似。首先用子查询找出分公司 B003 中原工工资的最大数值,然后,外查询找出工资高于这个数值的所有员工。在这个例子中使用了关键字 ALL。
多表查询
以上例子的一个最大问题是限定查询结果中出现的所有列必须来自同一个表。很多情况下,一个表不够。要把来自多个表的列组合到结果表时,就需要用到 连接 操作。SQL 连接操作通过配对相关行来合并两个表中的信息。而构成连接表的配对行是指这两行在两个表的匹配列上具有相同值。
要从多个表中得出查询结果,可用子查询,也可用连接操作。如果最终结果表包括了多个表中的列,则必须用连接操作。连接操作中,FROM 子句列出多个表明,之间用逗号分开,通常还要用 WHERE 子句来指明连接列。在 FROM 子句中也可用 别名 代替表名,它们之间用空格分开。别名可在列名有歧义的时候用来指定列名。别名也可以用来作为表名的简写。如果定义了别名,则可在任何地方用它代替表名。
例 6.24 简单连接:
列出查看过房产的所有客户的姓名及其所提的意见。
SELECT c.clientNo, fName, lName, propertyNo, comment
FROM Client c, Viewing v
WHERE c.clientNo = v.clientNo;
要显示来自表 Client 和表 Viewing 的细节信息需要用到连接。SELECT 子句中列出了需要显示的列。注意,必须在 SELECT 列表中限定员工编号 clientNo:由于 clientNo 可以来自两个表中任意一个,所以必须指定来自哪一个(选择表 Viewing中的员工标号 clientNo 也可以),在列名前缀以表名(或别名)即可实现这种限定。此例中,用 c 作为表 Client 的别名。
为得到所需的行,用查询条件(c.clientNo = v.clientNo)得到了两个表中在列 clientNo 上有相同值的那些行。该列也称为两个表的 匹配列。这类似于前面关系代数部分踢桃的相等连接(Equijoin)操作。
最普通的多表查询包括一对多(1:*)(或父 / 子)联系的两个表。前面涉及 client 和 viewing 关系的查询就是这样的例子。每一次看房(子)对应着一个客户(父),每个客户(父)可能多次去看房(子),产生的查询结果中的行对是父 / 子行的结合。主关键字所在的表是父表,外部关键字所在的表是子表。要在 SQL 查询中使用父 / 子联系,则需要指定比较主关键字和外部关键字的查找条件。
SQL 标准提供了下列可选择的方式来指定连接:
FROM Client c JOIN Viewing v ON c.clientNo = v.clientNo
FROM Client JOIN Viewing USING clientNo
FROM Client NATURAL JOIN Viewing
在每一种情况中,FROM 子句都替代原来的 FROM 和 WHERE 子句。只是第一种方式产生的表中有两个 clientNo 列,而其他两种方式产生的表中只有一个 clientNo 列。
例 6.25 排序连接结果:
对每一个分公司,列出管理房产的员工的姓名、编号及其正在管理的房产。
SELECT s.branchNo, s.staffNo, fName, lName, propertyNo
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
ORDER BY s.branchNo, s.staffNo, propertyNo;
这里为了使查询结果有更好的可读性,按分公司编号作为主排序关键字,员工编号和房产编号作为次关键字进行排序。
例 6.26 三表连接:
对每一个分公司列出管理房产的员工姓名、编号,以及分公司所在的城市和员工管理的房产。
SELECT b.branchNo, b.city, s.staffNo, fName, lName, propertyNo
FROM Branch b, Staff s, PropertyForRent p
WHERE b.branchNo = s.branchNo AND s.staffNo = p.staffNo
ORDER BY b.branchNo, s.staffNo, propertyNo;
查询结果所需的列来自三个表:Branch、Staff 和 PropertyForRent,所以必须用连接操作。用相等条件(b.branchNo = s.branchNo)连接表 Branch 和表 Staff,将每个分公司和在那里工作的员工连接起来,用相等条件(s.staffNo = p.staffNo)连接表 Staff 和表PropertyForRent,将每位员工和其管理的房产连接起来。
注意,SQL 标准为 FROM 和 WHERE 子句提供可选的表示法,例如:
FROM (Branch b JOIN Staff s USING branchNo) AS bs
JOIN PropertyForRent p USING staffNo
例 6.27 按多个列分组:
找出每一位员工管理的房产的数量,以及该员工所在的分公司编号。
SELECT s.branchNo, s.staffNo, COUNT(*) AS myCount
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
GROUP BY s.branchNo, s.staffNo
ORDER BY s.branchNo, s.staffNo;
为列出所需的数据,首先找到员工管理的房产。在 FROM / WHERE 子句中,用 staffNo 列连接表 Staff 和 PropertyForRent。下一步,用 GROUP BY 子句形成按分公司编号和员工编号的分组。最后,用 ORDER BY 子句排序。
连接运算的计算过程
连接操作其实是更一般的两表合并,所谓 笛卡尔积 的子集。两个表的笛卡尔积是包括两个表中所有可能的行对的一个新表。新表的列是第一个表的所有列后加上第二个表的所有列。如果两个表的查询不使用 WHERE 子句,那么 SQL产生的查询结果就是两个表的笛卡尔积。事实上,ISO 标准为笛卡尔积提供了特殊的 SELECT 语句格式:
SELECT [DISTINCT | ALL] {* | columnList}
FROM TableName1 CROSS JOIN TableName2
从概念上看,使用连接的 SELECT 语句查询过程如下:
- 形成 FROM 子句中指定的笛卡尔积。
- 如果存在 WHERE 子句,对乘积表的每一行运用条件查找,保留那些满足条件的行,用关系代数术语来说,这个操作即对笛卡尔积的 限制。
- 对于每个剩下的行,确定 SELECT 列表中每一项的值,并形成查询结果中的一行。
- 如果指定了 SELECT DISTINCT,则消除结果中重复的行。以关系代数看,第 3 步和第 4 步相当于把第二步得到的限制在 SELECT 列表列上进行 投影。
- 如果存在 ORDER BY 子句,则根据要求对查询结果进行排序。
外连接
连接操作通过配对相关的行来组合两个表中的数据,即找到在两个表的匹配列上具有相同值的行。如果表中某一行不匹配另一表的任何行,那么这行将从结果表中删除。这就是上面所讨论的连接问题。ISO 标准提供的另一类连接操作称为外连接。外连接保留不满足连接条件的行。为了更好地理解外连接操作, 请看下面两个简化的表 Branch 和 PropertyForRent,分别称为 Branch1 和 PropertyForRent1。
Branch1:
branchNo | bCity |
---|---|
B003 | Glasgow |
B004 | Bristol |
B002 | London |
PropertyForRent1:
propertyNo | pCity |
---|---|
PA14 | Aberdeen |
PL94 | London |
PG4 | Glasgow |
两个表的内连接如下:
SELECT b.*, p.*
FROM Branch1 b, PropertyForRent p
WHERE b.bCity = p.pCity;
产生的结果如下表所示:
branchNo | bCity | propertyNo | pCity |
---|---|---|---|
B003 | Glasgow | PG4 | Glasgow |
B002 | London | PL94 | London |
结果表输出两个表中相同城市的所有行。特别注意,没有行与 Bristol 的分公司匹配,也没有行与 Aberdeen 的房产匹配。如果希望不匹配的行也出现在结果表中,就需要用到外连接。外连接有三种类型:左外连接、右外连接 和 全外连接。
例 6.28 左外连接:
列出所有分公司及与其处于同一城市的房产。
这两个表的左外连接:
SELECT b.*, p.*
FROM Branch1 b LEFT JOIN PropertyForRent1 p ON b.bCity = p.pCity;
本例中左外连接不仅包括了那些城市列值相同的行,还包括了第一个(左边)表中与第二个(右边)表无匹配行的那些行。对这些行,来自第二个表的列上填 NULL。
例 6.29 右外连接:
列出所有房产以及与其同处一城的分公司。
这两个表的右外连接:
SELECT b.*, p.*
FROM Branch1 b RIGHT JOIN PropertyForRent1 p ON b.bCity = p.pCity;
右外连接不仅包括了相同城市列值得行,还包括了第二个(右边)表中与第一个(左边)表无匹配行的那些行。对这些行,来自第一个表的列上填写 NULL。
例 6.30 全外连接:
列出处于同一城市的分公司和房产,包括不匹配的分公司和房产。
这两个表的全外连接:
SELECT b.*, p.*
FROM Branch1 b FULL JOIN PropertyForRent1 p ON b.bCity = p.pCity;
全外连接不仅包括具有相同城市列值的行,还包括两个表中不匹配的行。这些不匹配的列上填写 NULL。
EXISTS 和 NOT EXISTS
关键字 EXISTS 和 NOT EXISTS 仅用于子查询中,返回结果为真 / 假。EXISTS 为真当且仅当子查询返回的结果表至少存在一行,当子查询返回的结果表为空时则为假。NOT EXISTS 正相反。由于 EXISTS 和 NOT EXISTS 仅检查子查询结果中是否存在行,所以子查询可查询任意数目的列。换句话说,子查询通常用下列形式表示:
(SELECT * FROM ...)
例 6.31 使用 EXISTS 查询:
找出工作在伦敦分公司的所有员工。
SELECT staffNo, fName, lName, position
FROM Staff s
WHERE EXISTS (SELECT *
FROM Branch b
WHERE s.branchNo = b.branchNo AND city = 'London');
查询语句可改写为 “找出所有这样的员工,其分公司编号为 branchNo,对应表 Branch 中的一行,并且该分公司所在的城市为伦敦”,该测试就是测试是否存在这样一行。如果存在,子查询为真。
注意,查找条件的第一部分 s.branchNo = b.branchNo 非常必要,可以确保员工属于指定的分公司。如果漏掉这一部分,则将会列出所有的员工,因为子查询(SELECT * FROM Branch WHERE city = 'London')总为真,该子查询将简化为:
SELECT staffNo, fName, lName, position FROM Staff WHERE true;
等价于
SELECT staffNo, fName, lName, position FROM Staff;
也可以用连接重写这个查询:
SELECT staffNo, fName, lName, position
FROM Staff s, Branch b
WHERE s.branchNo = b.branchNo AND city = 'London';
合并结果表(UNION、INTERSECT 和 EXCEPT)
SQL 中,可用标准的并、交和差集合操作将多个查询结果表合并成一个查询结果表:
- A 和 B 两个表的 并 操作是一个包括两个表中所有行的表。
- A 和 B 两个表的 交 操作是一个包括两个表中共有行的表。
- A 和 B 两个表的 差 操作是一个包括那些在 A 中而不在 B 中的行的表。
用于集合操作的表有一些限制,最重要的一点是两个表具有 并相容性,也就是说要具有相同的结构。即两个表必须包含相同数目的列,且对应的列具有相同的数据类型和长度。用户必须确保对应列的数值来自相同的 域。例如,将员工年龄的列与房产中房间数量进行组合是不明智的,尽管这两列有相同的数据类型,例如 SMALLINT。
ISO 标准中的三个集合运算符分别是 UNION、INTERSECT 和 EXCEPT,集合操作子句格式如下:
operator [ALL][CORRESPONDING [BY {column1 [,...]}]]
如果指定 CORRESPONDING BY,则集合操作就在给定的列上执行。若指定 ALL 则查询包括一切重复的行。一些 SQL 的实现版本并不支持 INTERSECT 和 EXCEPT,还有一些实现用 MINUS 取代了 EXCEPT。
例 6.32 UNION 的使用:
建立有分公司或有房产的所有城市的列表。
(SELECT city
FROM Branch
WHERE city IS NOT NULL)
UNION
(SELECT city
FROM PropertyForRent
WHERE city IS NOT NULL);
或
(SELECT *
FROM Branch
WHERE city IS NOT NULL)
UNION CORRESPONDING BY city
(SELECT *
FROM PropertyForRent
WHERE city IS NOT NULL);
此例从第一个查询中产生一个结果表,也从第二个查询中产生一个结果表,然后将两个表合并为一个由两个表中除去所有重复行组成的表。
例 6.33 INTERSECT 的使用:
建立既有分公司又有房产的所有城市的列表。
(SELECT city
FROM Branch
INTERSECT
(SELECT city
FROM PropertyForRent);
或
(SELECT *
FROM Branch
INTERSECT CORRESPONDING BY city
(SELECT *
FROM PropertyForRent);
此例从第一个查询中产生一个结果表,也从第二个查询中产生一个结果表,然后将两个表合并为一个由两个表所有共有行组成的表。
可以不用 INTERSECT 运算符重写这个查询,例如:
SELECT DISTINCT b.city
FROM Branch b, PropertyForRent p
WHERE b.city = p.city;
或
SELECT DISTINCT city
FROM Branch b
WHERE EXISTS (SELECT *
FROM PropertyForRent p
WHERE b.city = p.city);
可用各种等价的形式书写查询是 SQL 语言的一大缺陷。
例 6.34 EXCEPT 的使用:
建立具有分公司但没有房产的所有城市的列表。
(SELECT city
FROM Branch)
EXCEPT
(SELECT city
FROM PropertyForRent);
或
(SELECT *
FROM Branch)
EXCEPT CORRESPONDING BY city
(SELECT *
FROM PropertyForRent);
查询从第一个查询中产生一个结果表,也从第二个查询中产生一个结果表,然后将两个表合并为一个由在第一个表中而不在第二个表中的所有行组成的表。
可以不用 EXCEPT 运算符重写这个查询。例如:
SELECT DISTINCT city
FROM Branch
WHERE city NOT IN (SELECT city
FROM PropertyForRent);
或
SELECT DISTINCT city
FROM Branch b
WHERE NOT EXISTS (SELECT *
FROM PropertyForRent p
WHERE b.city = p.city);
数据库更新
SQL 是一种完全的数据操作语言,可用于修改数据库中的数据,也可用于查询数据库。修改数据库的命令不像 SELECT 语句那样复杂。下面讨论修改数据库内容的三种 SQL 语句。
- INSERT:向表中添加新的行。
- UPDATE:修改表中现有的行。
- DELETE:删除表中已有的行。
向数据库中添加数据(INSERT)
这里有两种 INSERT 语句形式,第一种是插入一个行的语句,格式如下:
INSERT INTO TableName [(columnList)]
VALUES (dataValueList)
TableName 是一个基表或是一个可更新的视图,columnList 代表用逗号分开的一个或多个列名,该项是可选的。如果省略 columnList,SQL 将严格按它们在 CREATE TABLE 命令中得顺序。如果给出 columnList,则在 columnList 中未出现的列在建表时不能声明为 NOT NULL,除非建该列时使用 DEFAULT 选项。dataValueList 必须与 columnList 有如下匹配:
- 列表 columnList 与 dataValueList 中项的数目必须是相同的。
- 两个列表中项的位置必须是直接对应的,dataValueList 中的第一项对应于 columnList 中的第一个项。dataValueList 中的第二项对应于 columnList 中的第二项,等等。
- dataValueList 列表中每一项的数据类型必须和对应列数据类型兼容。
例 6.35 INSERT ... VALUES:
向表 Staff 中插入包括所有列数据的一个新行。
INSERT INTO Staff
VALUES ('SG44', 'Alan', 'Brown', 'Assistant', 8100, 'M', DATE '1957-05-25', 8300, 'B003');
本例按照表创建时的列顺序插入数据,不必再指定列名列表。注意,Alan这样的字符常量必须用单引号引起来。
例 6.36 默认插入方式:
指定列 staffNo、fName、lName、position、salary 和 branchNo,向表 Staff 中插入新行。
INSERT INTO Staff (staffNo, fName, lName, position, salary, branchNo)
VALUES ('SG44', 'Anne', 'Jones', 'Assistant', 8100, 'B003');
若插入数据到某些列上,则必须制定需插入数据的列名字。列名的顺序并不重要,但是按他们在表中原本出现的顺序是比较常见的做法。也可以把 INSERT 语句写成下面的形式:
INSERT INTO Staff
VALUES ('SG44', 'Anne', 'Jones', 'Assistant', NULL, NULL, 8100, 'B003');
本例中显式的指定 sex 和 DOB 应设置为 NULL。
第二种 INSERT 语句允许把一个或多个表中的多个行复制到另一个表,格式如下:
INSERT INTO TableName [(columnList)]
SELECT ...
插入单个行之前,TableName 和 columnList 必须预先定义好,SELECT 子句可以是任何有效的语句。插入给定表中的行就是来自于子查询所生成的查询结果表。用于第一种形式的 INSERT 语句的约束在这里也有效。
例 6.37 INSERT ... SELECT:
假设表 StaffPropCount 中包含员工的名字及他所管理的房产的数目。
StaffPropCount(staffNo, fName, lName, propCount)
用表 Staff 和表 PropertyForRent 中的数据产生表 StaffPropCount。
INSERT INTO StaffPropCount
(SELECT s.staffNo, fName, lName, COUNT(*)
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
GROUP BY s.staffNo, fName, lName)
UNION
(SELECT staffNo, fName, lName, 0
FROM Staff s
WHERE NOT EXISTS (SELECT *
FROM PropertyForRent p
WHERE p.staffNo = s.staffNo));
因为需要计算出员工管理的房产数目,所以例子变得更加复杂。如果省略 UNION 的第二部分,则得到的是至少管理一处房产的员工的列表。换句话说,就是把没有管理过房产的员工排除掉了。所以,为了包括没有管理房产的员工,必须用 UNION 语句和第二个 SELECT 语句增加这样的员工,计数属性设为 0。
注意,一些 SQL 的实现版本不允许在 INSERT 子查询中使用 UNION 运算符。
修改数据库中的数据(UPDATE)
UPDATE 语句允许改变给定表中已存在的行的内容。命令格式如下:
UPDATE TableName
SET columnName1 = dataValue1 [, columnName2 = dataValue2 ...]
[WHERE searchCondition]
TableName 是基表或可更新视图的名字。SET 子句指定需要更新的一个或多个列的名字。WHERE 子句是可选择的,如果省略,则对给定列的所有行进行更新。如果给出 WHERE 子句,则仅对那些满足 searchCondition 的行进行更新,新的 dataValue 必须与对应列中的数据类型兼容。
例 6.38 更新所有行:
把所有员工的工资提高 3%。
UPDATE Staff
SET salary = salary * 1.03;
这个更新是对表中所有行的,故 WHERE 子句可省略。
例 6.39 更新指定的行:
把所有经理的工资提高 5%。
UPDATE Staff
SET salary = salary * 1.05
WHERE position = 'Manager';
WHERE 子句找出所有经理的行,然后把更新 salary = salary * 1.05 应用到特定的行。
例 6.40 更新多个列:
提升 David Ford(staffNo = 'SG14')为经理,工资变为 18000 英镑。
UPDATE Staff
SET position = 'Manager', salary = 18000
WHERE staffNo = 'SG14';
删除数据库中的数据(DELETE)
DELETE语句允许从给定表中删除行。命令格式如下:
DELETE FROM TableName
[WHERE searchCondition];
与 INSERT 和 UPDATE 语句一样,TableName 是基表或可更新视图的名字。searchCondition 是可选的,如果省略,则所有行都会被删除。注意,不是删除表本身,而是删除表的内容。若要既删除内容又删除表定义,可用 DROP TABLE 语句。如果指定了 searchCondition,则仅删除那些满足条件的行。
例 6.41 删除指定行:
删除所有与房产 PG4 有关的行。
DELETE FROM Viewing
WHERE propertyNo = 'PG4';
WHERE 子句找出房产编号为 'PG4' 的所有行,删除这些行。
例 6.42 删除所有行:
DELETE FROM Viewing;
不给出 WHERE 子句,将会删去表中所有行,只留下表定义,这样做的目的是使稍后仍可插入数据。