1.1 SQL 语言 1
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
1
SQL核心
凯伦·莫顿(Karen Morton)
不管你是刚开始写SQL语句还是已经写过很多年了,学会写出“ 好的” SQL这个过程都需要
具有很扎实的SQL核心语法和概念基础知识。本章对SQL语言的核心概念及其性能做了回顾,同
时还描述了一些你应该已经很熟悉的常用SQL命令。对于那些以前曾经使用过SQL并且基础知识
相当牢靠的读者来说,本章就是一个简要的复习,让你为后面更详细的SQL论述做好准备。如果
你是一位SQL新人,你可能想要先阅读Beginning Oracle SQL这本书以确保掌握SQL的基础。不管
是哪种情况,第1章的目的就是通过对5个核心SQL语句的快速浏览来衡量一下你的SQL水平,同
时还概述了我们用来执行SQL语句的工具:SQL*Plus。
1.1 SQL 语言
SQL语言最早是IBM公司于20世纪70年代开发出来的,称为结构化英文查询语言,简称为
SEQUEL。该语言是基于E.F.Codd在1969年提出的关系型数据库管理系统(RDBMS)的。后来因
为商标的纠纷,其简称又进一步缩写为SQL。1986年和1987年,ANSI(美国国家标准化组织)
和ISO(国际标准化组织)先后将SQL语言采纳为标准语言。而人们并不熟悉的是,ANSI官方曾
第 1 章
2 第 1 章 SQL 核心
将SQL语言的读音确定为“ S-Q-L” 。绝大多数人,包括我本人,都还在使用“ sequel” 的读音,
只是因为这样读起来更顺口一些。
SQL的目的就是简单地提供一个到数据库的接口,在本书指的是Oracle数据库。每一条SQL
语句对于数据库来说就是一条命令或指令。SQL与其他编程语言(如C或Java)的区别就在于它
是要处理数据集合而不是一行一行的数据。语言本身也不需要你提供如何导航到数据的指令——
这是在后台透明地进行的。但你将在后面的章节中看到,如果想在Oracle中写出高效的SQL语句,
了解数据及其在数据库中的存储方式与存储位置是很重要的。
由于不同的供应商(例如甲骨文、IBM和微软)实现SQL核心功能的机制相差无几,所以基
于某一种数据库所学的技巧同样可以应用到其他类型的数据库上。你基本上可以利用同样的SQL
语句来进行数据的查询、插入、更新和删除,以及创建、修改和删除对象,而不必管数据库的供
应商是哪家。
尽管SQL是各种关系型数据库管理系统的标准语言,但实际上它并不一定是关系型的。在本
书后面我将就这一点稍作扩展。如果想要了解更多的细节,我推荐大家阅读C.J.Date的SQL and
Relational Theory一书。需要铭记于心的一点是SQL语言并不总是严格遵守关系模型的——它根本
就没有实现关系模型的某些要素,同时还不恰当地实现了一些要素。事实上,既然SQL是基于关
系模型的,那么要想写出尽可能正确高效的SQL语句,你不仅必须要理解SQL语言,还要理解关
系模型。
1.2 数据库的接口 3
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
1.2 数据库的接口
多年以来人们开发出多种途径来传递SQL语句到数据库并获得结果。Oracle数据库的本地接
口界面是Oracle调用界面(OCI)。OCI将由Oracle内核传送而来的查询语句发送到数据库。当使
用某种Oracle工具如SQL*Plus或者SQL Developer时,你都在使用OCI。其他的Oracle工具如
SQL*Loader、数据泵(Data Pump)以及Real Application Testing (RAT)既使用OCI,也可以使
用语言特定的接口,如Oracle JDBC-OCI、ODP.Net、Oracle预编译器、Oracle ODBC以及Oracle C++
调用接口(OCCI)驱动器。
当使用编程语言(如COBOL或C语言)时,你所写的语句被称为嵌入式的SQL语句并且在应
用程序编译之前会由SQL预处理器进行预处理。代码清单1-1是一段可以在C/C++程序块中使用的
SQL语句的例子。
代码清单1-1 C/C++程序块中所嵌入的SQL语句
其他工具,例如SQL*Plus和SQL Developer,都是交互式的工具。你输入并执行命令,然后
获得相应的输出。交互式工具并不需要在运行代码前先精确编译,你只需要输入想要执行的命令
即可。代码清单1-2是一段使用SQL*Plus执行语句的例子。
4 第 1 章 SQL 核心
代码清单1-2 使用SQL*Plus执行SQL语句
在本书中,为了保持一致性我们所用的示例代码清单都使用SQL*Plus工具,但需要记住的是,
不管你是用什么方法或工具来输入和执行SQL语句,所有的事情最后都要通过OCI来传递到数据
库。这里的主旨就是不管你所使用的是什么工具,其本地接口都是一样的。
1.3 SQL*Plus 回顾
SQL*Plus是一个不管采用哪个安装平台(Windows或Unix)都会提供的命令行工具。它是一
个用来输入和执行SQL语句并显示输出结果的纯文本环境。用该工具可以直接输入、编辑命令,
可以一条条地保存和执行命令或者通过脚本文件来进行,然后将输出结果以很精美格式的报表输
出。要启动SQL*Plus你只需要在主机的命令提示符后敲入sqlplus即可。
1.3.1 连接到数据库
有多种方法可以通过SQL*Plus连接数据库。然而在连接之前,你还需要在$ORACLE_HOME/
network/admin/tnsnames.ora这个文件中登记想要连接的数据库。有两种通常使用的方法,
或者如代码清单1-3所示那样在启动SQL*Plus时提供连接信息,或者如代码清单1-4所示那样在启
动SQL*Plus以后使用connect命令。
1.3 SQL*Plus 回顾 5
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
代码清单1-3 通过窗口命令提示符连接到SQL*Plus
如果想要启动SQL*Plus而又不显示登录到数据库后的提示,可以在启动SQL*Plus时使用
/nolog选项。
代码清单1-4 通过SQL>提示符连接SQL*Plus并登录到数据库
1.3.2 配置 SQL*Plus 环境
SQL*Plus有很多的命令可以让你来定制工作环境和显示选项。代码清单1-5所示是在SQL>提
示符下输入help index命令后显示出来的可用的命令。
代码清单1-5 SQL*Plus命令列表
6 第 1 章 SQL 核心
set命令是用来定制工作环境的最基本的命令。代码清单1-6为set命令的帮助文本。
代码清单1-6 SQL*Plus的SET命令
1.3 SQL*Plus 回顾 7
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
有了上面这些可用命令,你就能够很轻松地定制最适合你的运行环境了。但有一点要铭记于
心的就是当你退出或关闭SQL*Plus的时候,这些设置命令就不再被保留了。为了避免每次使用
SQL*Plus时都重新敲入一遍这些设置命令,你可以创建一个login.sql文件。事实上每次启动
SQL*Plus的时候它都会默认去读两个文件。第一个是$ORACLE_HOME/sqlplus/admin目录下的
glogin.sql文件。如果找到了这个文件,它就会被读进来,文件中的命令语句也会被执行。这
样就可以把那些定制你的会话体验的SQL*Plus命令和SQL语句保存起来。
在读取glogin.sql文件以后,SQL*Plus会进一步寻找login.sql文件。这个文件必须在
SQL*Plus的启动文件夹中或者包含在环境变量SQLPATH所指向的文件夹路径中。在login.sql
文件中的所有命令优先级都比glogin.sql文件中的命令高。从10g开始,Oracle在每次你启动
SQL*Plus 或 者 从 SQL*Plus 里 执 行 connect 命 令 的 时 候 都 会 同 时 去 读 取 glogin.sql 和
8 第 1 章 SQL 核心
login.sql这两个文件。在Oracle 10g之前,login.sql脚本文件只有在SQL*Plus启动的时候才
会被执行。代码清单1-7是一个常见的login.sql文件内容。
代码清单1-7 一个常见的login.sql文件
注意这里在SET SQLPROMPT中使用的变量_user和_connect_identifier。它们是预定义
变量的两个示例。你可以在login.sql文件中或者任何你创建的脚本文件中使用下面这些预定义
变量:
_connect_identifier
_date
_editor(这个变量指定了当你使用edit命令的时候启动哪个编辑器)
_o_version
_o_release
_privilege
_sqlplus_release
_user
1.3.3 执行命令
有两种命令可以在SQL*Plus中执行:SQL语句和SQL*Plus命令。代码清单1-5和代码清单1-6
中所列出的SQL*Plus命令对于SQL*Plus来说是特有的命令,可以用来定制运行环境并且可以运行
SQL*Plus特有的命令,例如DESCRIBE和CONNECT。要想执行一个SQL*Plus命令,你只需在命令
1.3 SQL*Plus 回顾 9
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
提示符后输入该命令然后敲回车,命令会自动被执行。另一方面,如果要执行SQL语句,就必须
使用一个特定字符来表明你想要执行输入的语句,分号(;)或者斜线(/)都可以。使用分号的
话可以直接放在输入命令的后面或者放在接下来的空行中,而斜线则必须放在接下来的空行中才
可以被识别。代码清单1-8展示了如何使用这两种符号。
代码清单1-8 执行字符的用法
注意第5个在语句最后面加了一个斜线(/)的例子。光标移动到了下一行而不是立即执行语
句命令。接下来,如果你再按一下回车键,语句就会被放入SQL*Plus的缓冲器中,但是也不执行。
10 第 1 章 SQL 核心
如果想要查看SQL*Plus缓冲器中的内容,可以使用list命令(也可以简写为l)。接下来如果你想
在缓冲器中通过使用斜线(/)来执行语句[尽管斜线(/)命令本来就是这样来用的]在这里也将
会返回一个错误。这是因为你最初在SQL语句的结尾敲入了一个斜线(/),而斜线(/)并不是一
个有效的SQL命令,从而在语句想要执行的时候报错。
另外一种执行命令的方法是把命令放到一个文件中。你可以在SQL*Plus之外直接用文本编辑
器生成这些文件,也可以在SQL*Plus中使用EDIT命令来直接调用编辑器。如果已经有了一个文件,
EDIT命令可以打开这个文件,如果没有的话就会创建新的文件。文件必须放在默认文件夹中,否
则你必须指定文件的全路径。想要设定所 选择的编辑器,你只需要利用命令define_
editor='/<full path>/myeditor.exe'来设置预定义变量_editor。具有.sql扩展名的文件
在执行的时候不必敲入扩展名,通过@或START命令都可以执行。代码清单1-9中列出了这两个命
令的用法。
代码清单1-9 执行.sql脚本文件
1.4 5 个核心的 SQL 语句 11
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
SQL*Plus具有很多特性和选项,以致于多得在这里不能一一列举。就本书需要而言,这种概
述就已经足够了。但是,Oracle文档对SQL*Plus的用法给出了指导,而且很多的书,比如Beginning
Oracle SQL,都对SQL*Plus作了更为深入的阐述,如果感兴趣你可以参考。
1.4 5 个核心的 SQL 语句
SQL语言有很多不同的语句,但在整个职业生涯中,你可能只会用到其中很少的一部分。不
过你所使用的几乎其他任何产品不也是这样的吗?据说有一个统计结果是,绝大多数人都仅使用
了他们常用的软件产品或编程语言所有功能的20%甚至更少。我不知道这个统计真实与否,但以
我的经验来看,这似乎是很准确的。我发现同样的基本SQL语句格式在大多数应用中使用了将近
20年了。极少数的人使用过SQL提供的所有功能——即使对于那些他们确实经常使用的功能也常
常用得不是很恰当。显而易见,我们不可能覆盖SQL语言的所有语句以及它们的选项。本书的目
的在于让你能够深入理解那些最常用的SQL语句并帮助你更高效地使用它们。
在本书中,我们将重点讨论5个最常用的SQL语句,它们分别为SELECT、INSERT、UPDATE、
DELETE以及MERGE。尽管这些核心语句都将逐个讲解,但重中之重还是SELECT语句。将这5个
语句用好了将会为你在日常工作中用好SQL语言打下坚实的基础。
12 第 1 章 SQL 核心
1.5 SELECT 语句
SELECT语句用来从一个或多个表中或者其他数据库对象中提取数据。你应该已经很熟悉
SELECT语句的基础知识了,所以我将不再从一个初学者的角度来介绍SELECT语句,而是首先回
顾一下SELECT语句的执行逻辑。对于如何来写一个基本的SELECT语句你应该已经学习过了,但
为了培养基本的思维模式,你要一直写出符合语法规则的高效SQL语句,你需要理解SQL语句是
如何执行的。
一个查询语句在逻辑上的处理方式可能会与实际物理处理过程大相径庭。Oracle基于查询成
本的优化器(cost-based optimizer , CBO)用来产生实际的执行计划。我们在后面的章节中将会讲
解优化器是干什么的,如何来实现其功能的以及为什么要进行优化。目前,我们需要关心的是优
化器将会决定如何访问表、按照什么样的顺序来处理它们,以及如何将多个表联结起来及如何使
用筛选器。查询的处理在逻辑上是按照特定的顺序进行的,但是,优化器所选择的物理执行计划
可能会按照完全不同的顺序来实际执行这些步骤。代码清单1-10是一段包含SELECT语句的主要
子句的查询片段,在其中标出了每一个子句的逻辑处理顺序。
代码清单1-10 查询语句的逻辑处理顺序
1.5 SELECT 语句 13
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
你应该立刻注意到SQL有别于其他编程语言的一点在于首先处理的并不是写在第一行的语
句(SELECT语句),而是FROM子句。注意在这个代码清单中我给出了两个不同的FROM子句。标
记为1.1的那个FROM子句表示的是当使用ANSI语法时的不同。我们可以把处理过程中的每一个步
骤想象为生成一个临时的数据集。随着每个处理步骤的进行,这个数据集被不断地操作直到生成
最终的处理结果。查询返回给调用者的就是这个最终结果数据集。
为了更详细地了解SELECT语句的每个部分,你可以参考代码清单1-11所示的查询语句,该
语句返回的结果集为下订单超过4次的女顾客的列表。
代码清单1-11 下订单超过4次的女顾客查询语句
1.5.1 FROM 子句
FROM子句列出了所查询数据的源对象。这个子句可以包含表、视图、物化视图、分区或子
分区,或者你可以建立一个子查询来生成子对象。如果使用了多个源对象,其逻辑处理阶段也将
会应用到每一个联结类型以及谓词ON(如步骤1.1所示)。在本书后面的章节中你将会进一步了解
联结类型的更多细节,但注意在处理联结语句的时候是按照下面的顺序来进行的:
14 第 1 章 SQL 核心
(1) 交叉联结,也称为笛卡儿乘积;
(2) 内联结;
(3) 外联结。
在代码清单1-11所示的查询例子中,FROM子句列出了两张表:customers和orders,通过
customer_id列来联结。因此,当处理这一信息时,FROM子句所生成的初始数据集将会包含这
两张表中customer_id相匹配的行。在本例中结果集将会包含105行。为了验证这一点,只要执
行例子中的前4行,如代码清单1-12所示。
代码清单1-12 仅通过FROM子句的部分查询语句的执行
1.5 SELECT 语句 15
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
注意 为了使之很好地适应页面我手工调整了输出结果,实际输出结果在页面上超过105行。
1.5.2 WHERE 子句
WHERE子句提供了一种方法,可以按照条件来限制查询最终返回结果集的行数。每个条件或
者谓语都是以两个值或表达式相比较的形式出现的。比较的结果要么是匹配(值为TRUE)要么
是不匹配(值为FALSE)。如果比较的结果是FALSE,那么相应的行不会被包含在最终结果集中。
这里我需要稍微偏离一下主题,来谈一谈与这一步相关的SQL中的一个重要方面。事实上,
16 第 1 章 SQL 核心
SQL中逻辑比较的可能结果是TRUE、FALSE以及未知。当其中包含空值(null)的时候比较的结
果就会是未知。空值与任何值比较或者用在表达式中都会得到空值,或者是未知。一个空值代表
一个相应值的缺失,并且可能因为SQL语言中的不同部分对空值的处理不同而令人费解。关于空
值是如何影响SQL语句执行的话题将会贯穿本书,但在这里我不得不先提及一下这个话题。我之
前所说的还是基本正确的,一个比较的返回值将会是TRUE或者FALSE。你会发现当进行筛选的比
较条件中包含空值的时候,将作为FALSE来对待。
在我们的例子中,只有一个将结果限定为下了订单的女性消费者的谓语。如果你查看FROM
子句执行之后的中间结果(见代码清单1-12),你会发现105行中仅有31行是由女性消费者所下的
订单(gender = 'F')。因此,在应用了WHERE子句以后,中间结果集将从105行减少到31行。
应用WHERE子句以后得到了更精确的结果集。注意,在这里使用的是“ 精确的结果集” 。我
的意思是说现在已经得到了能够满足你查询需求的数据行。其他子句(GROUP BY, HAVING)也
许可以用来聚合并且进一步限制调用程序会接收到的最终的结果集,但需要注意的很重要的一点
是,目前已经得到了查询计算最终结果所需的所有数据。
WHERE子句的目的是限制或者减小结果集。你所使用的限制条件越少,最终返回的结果集中
包含的数据就会越多。你需要返回的数据越多,执行查询的时间也就越长。
1.5.3 GROUP BY 子句
GROUP BY子句将执行FROM和WHERE子句后得到的经过筛选后的结果集进行聚合。查询出来
的结果按照GROUP BY子句中列出的表达式进行分组,来为每一个分组得出一行汇总结果。你可
1.5 SELECT 语句 17
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
以按照FROM子句中所列出对象的任意字段进行分组,即使你并不想在输出结果列表中显示该列。
相反,Select列表中的任何非聚合字段都必须包括在GROUP BY表达式中。
GROUP BY子句中还可以包含两个附加的运算:ROLLUP 和CUBE。ROLLUP运算用来产生部分
求和值,CUBE运算用来求得交互分类值。当你使用这两种运算中任意一个的时候,你将会得到
不止一行的汇总信息。在第7章中将会对这两个运算进行更详细的讨论。
在示例查询中,需要按照customer_id来进行分组。这就意味着对于每一个唯一的
customer_id只会返回一行值。在WHERE子句执行后所得到的代表下订单的女性消费者的31行订
单中,有11个独特的customer_id值,如代码清单1-13所示。
代码清单1-13 截至GROUP BY子句的部分查询执行
18 第 1 章 SQL 核心
你会发现查询的结果是经过分组的,但并没有排序。表面上看结果好像是按照order_ct字
段排序的,但这仅仅是个巧合而不是确定的行为。需要记住的很重要的一点是:GROUP BY子句
并不确定结果数据的排序。如果你需要结果按照特定的顺序排列,则必须指定一个order by子
句。
1.5.4 HAVING 子句
HAVING子句将分组汇总后的查询结果限定为只有该子句中的条件为真的数据行。除非你使
用HAVING子句,否则将返回所有的汇总行。事实上,GROUP BY子句和HAVING子句的位置是可
以互换的,谁先谁后都无关紧要。但是,似乎在编码中将GROUP BY子句放在前面更有意义一些,
因为GROUP BY子句在逻辑上是先执行的。从本质上来说,HAVING子句是在GROUP BY子句执行
后用来筛选汇总值的第二个WHERE子句。
在我们的查询例子中,HAVING子句HAVING COUNT(o.order_id) > 4,将分组数据从11
行减少到2行。这一点你可以通过查看GROUP BY子句应用后返回的结果行来确认,如代码清单1-13
所示。注意仅有146和147号消费者所下的订单数超过4次。这样就产生了组成最终结果集的两行
数据。
1.5.5 SELECT 列表
SELECT列表列出查询的返回最终结果集中需要显示哪些列。这些列可以是数据表中一个实
际的列、一个表达式,或者甚至是一个SELECT语句的结果,如代码清单1-14所示。
代码清单1-14 展现SELECT列表各种可能情况的查询实例
SQL> select.customer_id, c.cust_first_name||''||c.cust_last_name,
1.5 SELECT 语句 19
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
.
当使用另外一个SELECT语句来产生结果中的一列的值的时候,这个查询必须只能返回一行
一列的值。这种类型的子查询被称为标量子查询。尽管这可能是一个非常有用的语法,但需要牢
记于心的是标量查询在结果集中的每一行结果产生时都要执行一遍。在某些情况下可以进行优化
以减少标量子查询的重复执行,但更糟糕的场景是每一行都需要标量子查询执行。你可以想象如
果你的结果集中有几千行甚至上百万行数据的时候所需要付出的查询代价!在后面的章节中我们
还将回顾标量子查询并讨论如何更好地来使用它们。
在SELECT列表中你还有可能用到的一个选项是DISTINCT子句。在例子中并没有使用它,但
我想要简要地提及一下。DISTINCT子句用来在其他子句执行完毕以后从结果集中去除重复的行。
SELECT列表执行完以后,你就得到了最终的查询结果集。所剩的唯一需要做的事情,如果
包含了的话,就是将查询结果集按照所需的顺序排序。
1.5.6 ORDER BY 子句
ORDER BY子句用来对查询最终返回的结果集进行排序。在本例中,需要按照orders_ct和
customer_id进行排序。orders_ct这一列是通过GROUP BY子句中的COUNT聚合函数计算得到
的值。如代码清单1-13中所示,有两个消费者的订单超过4个。由于这两个消费者的订单数都是5
份,orders_ct这一列的值是相同的,所以要由第二个排序列来确定最终结果的显示顺序。如代
码清单1-15中所示,该查询的最终经过排序的输出结果是按照customer_id排序的两行数据集。
代码清单1-15 示例查询的最终输出
20 第 1 章 SQL 核心
当输出结果需要排序的时候,Oracle必须在其他所有子句都执行完之后按照指定的顺序对最
终结果集进行排序。需要排序的数据量大小是非常重要的。我这里所说的大小是指结果集中所包
含的总字节数。你可以通过用行数乘以每一行的字节数来估计数据集的大小。每行所包含的字节
数通过将选择列表中包含的每一列的平均长度相加来确定。
上面的查询实例在选择列表中仅需要列出customer_id 和orders_ct两列的值。我们可以
估算每一行输出值的字节数为10。在第6章中我将阐述从哪里能找到优化器所估计的值。因此,
如果我们在结果集中只有两行数据,排序的大小实际上是很小的,大约20字节。请记住这仅仅是
估算,但这样的估算也是很重要的。
较小的排序会完全在内存中来实现,而较大的排序将不得不使用临时磁盘空间来完成。如你
可能推断的那样,在内存中完成的排序比必须使用磁盘的排序要快。因此,当优化器估算排序数
据的影响时,它必须要考虑排序数据集的大小,以此来调整如何能够以最有效的方法来获得查询
的结果。一般来说,排序是查询过程中开销相当大的一个处理步骤,尤其是当返回结果集很大的
时候。
1.6 INSERT 语句 21
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
1.6 INSERT 语句
INSERT语句用来向表、分区或视图中添加行。可以向单表或者多个表方法中添加数据行。
单表插入将会向一个表中插入一行数据,这行数据可以显式地列出插入值也可以通过一个子查询
来获取。多表插入将会向一个或多个表中插入行,并且会通过子查询获取值来计算所插入行的值。
1.6.1 单表插入
代码清单1-16中的第一个例子阐明了使用values子句实现的单表插入。每一列的值都显式
地输入。如果你要插入表中所定义的所有列的值,那么列的列表是可选的。但是,如果你只想提
供部分列的值,则必须在列的列表中指明所需的列名。好的做法是不管是不是需要插入所有列的
值,都把所有列的列表列出来。这样做就像该语句的自述文件一样,并且也可以减少将来别人要
插入一个新列到表中的时候可能出现的错误。
代码清单1-16 单表插入
第二个例子阐述了通过子查询来实现插入。这是插入数据行的一个非常灵活的选项。所写的
子查询可以返回一行或多行数据。返回的每一行都会用来生成需要插入的新行的列值。根据你的
需要这个子查询可以很简单也可以很复杂。在本例中,我们使用子查询实现了在现有薪水的基础
22 第 1 章 SQL 核心
上为每一位员工发放10%奖金的计算。事实上奖金表包含4列,但在这个插入中我们只列出了3个
字段。comm这一列在子查询中并没有占据一列并且我们也没有将它包括在列表中。因为我们没
有包含这一列,它的值将会是null。注意如果comm列具有非空约束,那么可能已返回一个约束错
误,语句的执行也已失败。
1.6.2 多表插入
代码清单1-17所示的多表插入的例子阐明了一个子查询返回的数据行是如何被用来插入多
个 表 中 的 。 我 们 从 3 个 表 开 始 : small_customers 、 medium_customers 以 及
large_customers。我们想要按照每位消费者所下订单的总金额来将数据分别插入这些表。子
查询将每一位消费者的order_total列求和来确定该消费者的消费金额是小(所有订单的累加
金额小于10 000美元)、中等(介于10 000美元与99 999.99美元之间)还是大(大于等于100 000
美元),然后按照条件将这些行插入对应的表中。
代码清单1-17 多表插入
1.6 INSERT 语句 23
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
24 第 1 章 SQL 核心
1.7 UPDATE 语句 25
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
注意INSERT关键字后面ALL子句的使用。当指定了ALL子句的时候,这个语句就会执行无条
件的多表插入。也就意味着每一个WHEN子句按照子查询所返回的每一行来确定值而不管前一个
条件的输出结果是什么。因此,你需要注意如何来指定每个条件。例如,如果我使用WHEN
sum_orders < 100 000这个条件而不是像上面一样列出范围,插入medium_customers表中
的行有可能也会插入small_customers表中。
你需要指明FIRST选项来实现每一个WHEN子句按照其出现在语句中的顺序进行评估,并且对
于一个给定的子查询行跳过接下来的WHEN子句评估。关键在于要记住哪一个选项能够更好地满
足你的需要,ALL还是FIRST,然后使用最适合的选项。
1.7 UPDATE 语句
UPDATE语句的作用是改变表中原有行的列值。这个语句的语法由3部分组成:UPDATE、SET
和WHERE。UPDATE子句用来指定要更新的表,SET子句用来指明哪些列改变了以及调整的值,
WHERE子句用来按条件筛选需要更新的行。WHERE子句是可选的,如果忽略了这个子句的话,更
新操作将针对指定表中的所有行进行。
代码清单1-18列出了几种UPDATE语句的不同写法。首先,我建立了一个employees表的副本,
名称为employees2,然后我将执行几个完成基本相同任务的不同更新操作:将90部门的员工工资
增加10%。在例5中,commission_pct这一列也进行了更新。下面就是采用的不同方法。
例1:使用表达式更新一个单列的值。
例2:通过子查询更新一个单列的值。
例3:通过在WHERE子句中使用子查询确定要更新的数据行来更新单列的值。
例4:通过使用SELECT语句定义表及列的值来更新表。
例5:通过子查询更新多列。
代码清单1-18 UPDATE语句的例子
26 第 1 章 SQL 核心
1.7 UPDATE 语句 27
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
28 第 1 章 SQL 核心
1.8 DELETE 语句 29
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
1.8 DELETE 语句
DELETE语句用来从表中移除数据行。该语句的语法结构由3部分组成:DELETE、FROM和
WHERE。DELETE关键字是单独列出的。除非你决定使用我们后面将会讨论到的提示(hint),没
有其他选项与DELETE关键字相结合。FROM子句用来指定要从哪个表中删除数据行。如代码清单
1-19中的例子所示,这个表可以直接指定也可以通过子查询来确定。WHERE子句提供筛选条件有
助于确定哪些行是要删除的。如果忽略了WHERE子句,删除操作将删除指定表中的所有数据行。
代码清单1-19展示出了DELETE语句的几种不同写法。注意,在这些例子中我使用了代码清
单1-18中创建的employees2表。下面你将看到的就是这些不同的删除方法。
例1:使用WHERE子句中的筛选条件来从指定表中删除行。
30 第 1 章 SQL 核心
例2:使用FROM子句中的子查询来删除行。
例3:使用WHERE子句中的子查询来从指定表中删除行。
代码清单1-19 DELETE语句的例子
1.8 DELETE 语句 31
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
32 第 1 章 SQL 核心
1.9 MERGE 语句
MERGE语句具有按条件获取要更新或插入到表中的数据行,然后从1个或多个源头对表进行
更新或者向表中插入行两方面的能力。它最经常被用在数据仓库中来移动大量的数据,但它的应
用不仅限于数据仓库环境下。这个语句提供的一个很大的附加值在于你可以很方便地把多个操作
结合成一个。这就使你可以避免使用多个INSERT、UPDATE以及DELETE语句。并且,在本书后
面的内容中你将看到,如果你避免去做那些不是必须做的事情,响应时间可能得到相应的改善。
MERGE语句的语法是:
1.9 MERGE 语句 33
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
为了说明MERGE语句的用法,代码清单1-20展示出了如何建立一个测试表,然后恰当地利用
MERGE条件来向表中插入或更新行。
代码清单1-20 MERGE语句例子
34 第 1 章 SQL 核心
MERGE语句完成了下面这些事情。
插入了两行(员工id 106和107)。
1.10 小结 35
1
2
3
4
5
8
10
16
9
11
6
7
12
14
13
15
更新了一行(员工id 105)。
删除了一行(员工id 103)。
一行保持不变(员工id 104)。
如果没有MERGE语句,你必须最少写3条不同的语句来完成同样的事情。
1.10 小结
正如你可以从到目前为止的例子中看出的,SQL语言提供了很多不同的选择来得到同样的结
果集。你可能还注意到了一点就是这5个核心的SQL语句都可以使用类似的构造,例如子查询。
关键是需要搞清楚在各种不同的使用场景下哪种构造是最高效的。我们将在本书后面的内容中阐
述如何做到这一点。
如果你对本章的例子的理解有任何困难,请一定花点时间复习Beginning Oracle SQL或者
Oracle文档中的SQL Reference Guide。在本书中接下来的部分我们假设你已经很好地理解了5个核
心SQL语句的基本构造:SELECT、INSERT、UPDATE、DELETE和MERGE。