算术能力:在 SQL 里,我们可以和比较功能一样进行算术运算,例如 A < B + 3。要注意 + 或者其他算术操作符从未在关系代数或者关系微积分里面出现过。
分配和打印命令:我们可以打印一条查询构造的关系以及给一个被计算的关系分配关系名。
聚集函数:象 average,sum,max,等操作符可以应用于一个关系的列以获取单一的量。
SELECT [ALL|DISTINCT] { * | expr_1 [AS c_alias_1] [, ... [, expr_k [AS c_alias_k]]]} FROM table_name_1 [t_alias_1] [, ... [, table_name_n [t_alias_n]]] [WHERE condition] [GROUP BY name_of_attr_i [,... [, name_of_attr_j]] [HAVING condition]] [{UNION [ALL] | INTERSECT | EXCEPT} SELECT ...] [ORDER BY name_of_attr_i [ASC|DESC] [, ... [, name_of_attr_j [ASC|DESC]]]];现在我们将通过不同的例子演示 SELECT 语句复杂的语法。用于这些例子的表在 供应商和部件数据库 里定义。
例 69-4. 带有条件的简单查询
要从表 PART 里面把字段 PRICE 大于 10 的所有记录找出来,我们写出下面查询:
SELECT * FROM PART WHERE PRICE > 10;然后得到表:
PNO | PNAME | PRICE -----+---------+-------- 3 | Bolt | 15 4 | Cam | 25在 SELECT语句里使用 "*" 将检索出表中的所有属性。如果我们只希望从表 PART 中检索出属性 PNAME 和 PRICE,我们使用下面的语句:
SELECT PNAME, PRICE FROM PART WHERE PRICE > 10;这回我们的结果是:
PNAME | PRICE --------+-------- Bolt | 15 Cam | 25请注意 SQL 的 SELECT 语句对应关系代数里面的 "projection" (映射),而不是 "selection"(选择)(参阅 关系代数 获取详细信息)。
WHERE 子句里的条件也可以用关键字 OR,AND,和 NOT 逻辑的连接起来:
SELECT PNAME, PRICE FROM PART WHERE PNAME = 'Bolt' AND (PRICE = 0 OR PRICE < 15);这样将生成下面的结果:
PNAME | PRICE --------+-------- Bolt | 15目标列表和 WHERE 子句里可以使用算术操作。例如,如果我们想知道如果我们买两个部件的话要多少钱,我们可以用下面的查询:
SELECT PNAME, PRICE * 2 AS DOUBLE FROM PART WHERE PRICE * 2 < 50;这样我们得到:
PNAME | DOUBLE --------+--------- Screw | 20 Nut | 16 Bolt | 30请注意在关键字 AS 后面的 DOUBLE 是第二个列的新名字。这个技巧可以用于目标列表里的每个元素,给它们赋予一个在结果列中显示的新的标题。这个新的标题通常称为别名。这个别名不能在该查询的其他地方使用。
要在共同的属性上联合三个表 SUPPLIER,PART 和 SELLS,我们通常使用下面的语句:
SELECT S.SNAME, P.PNAME FROM SUPPLIER S, PART P, SELLS SE WHERE S.SNO = SE.SNO AND P.PNO = SE.PNO;而我们得到的结果是:
SNAME | PNAME -------+------- Smith | Screw Smith | Nut Jones | Cam Adams | Screw Adams | Bolt Blake | Nut Blake | Bolt Blake | Cam在 FROM 子句里,我们为每个关系使用了一个别名,因为在这些关系间有着公共的命名属性(SNO 和 PNO)。现在我们可以区分不同表的公共命名属性,只需要简单的用每个关系的别名加上个点做前缀就行了。联合是用与 一个内部联接 里显示的同样的方法计算的。首先算出笛卡儿乘积 SUPPLIER × PART × SELLS。然后选出那些满足 WHERE 子句里给出的条件的记录(也就是说,公共命名属性的值必须相等)。最后我们映射出除 S.SNAME 和 P.PNAME 外的所有属性。
如果我们想知道表 PART 里面所有部件的平均价格,我们可以使用下面查询:
SELECT AVG(PRICE) AS AVG_PRICE FROM PART;结果是:
AVG_PRICE ----------- 14.5如果我们想知道在表 PART 里面存储了多少部件,我们可以使用语句:
SELECT COUNT(PNO) FROM PART;得到:
COUNT ------- 4
对记录的分组是通过关键字 GROUP BY 实现的, GROUP BY 后面跟着一个定义组的构成的属性列表。如果我们使用语句
GROUP
BY A1, &tdot, Ak,我们把关系分成了组,这样如果两条记录在(也只是在)所有属性
A1, &tdot, Ak 上达成一致,它们是同一组的。
如果我们想知道每个供应商销售多少个部件,我们可以这样写查询:
SELECT S.SNO, S.SNAME, COUNT(SE.PNO) FROM SUPPLIER S, SELLS SE WHERE S.SNO = SE.SNO GROUP BY S.SNO, S.SNAME;得到:
SNO | SNAME | COUNT -----+-------+------- 1 | Smith | 2 2 | Jones | 1 3 | Adams | 2 4 | Blake | 3现在我们看一看发生了什么事情。首先生成表 SUPPLIER 和 SELLS 的联接:
S.SNO | S.SNAME | SE.PNO -------+---------+-------- 1 | Smith | 1 1 | Smith | 2 2 | Jones | 4 3 | Adams | 1 3 | Adams | 3 4 | Blake | 2 4 | Blake | 3 4 | Blake | 4然后我们把那些属性 S.SNO 和 S.SNAME 相同的记录放在组中:
S.SNO | S.SNAME | SE.PNO -------+---------+-------- 1 | Smith | 1 | 2 -------------------------- 2 | Jones | 4 -------------------------- 3 | Adams | 1 | 3 -------------------------- 4 | Blake | 2 | 3 | 4在我们的例子里,我们有四个组并且现在我们可以对每个组应用聚集操作符 COUNT,生成上面给出的查询的最终结果。
请注意如果要让一个使用 GROUP BY 和聚集操作符的查询的结果有意义,那么用于分组的属性也必须出现在目标列表中。所有没有在 GROUP BY 子句里面出现的属性都只能通过使用聚集函数来选择。另一方面,你不能对出现在 GROUP BY 子句里面的属性使用聚集函数。
如果我们想知道那些销售超过一个部件的供应商,使用下面查询:
SELECT S.SNO, S.SNAME, COUNT(SE.PNO) FROM SUPPLIER S, SELLS SE WHERE S.SNO = SE.SNO GROUP BY S.SNO, S.SNAME HAVING COUNT(SE.PNO) > 1;得到:
SNO | SNAME | COUNT -----+-------+------- 1 | Smith | 2 3 | Adams | 2 4 | Blake | 3
如果我们想知道所有比名为 'Screw' 的部件贵的部件,我们可以用下面的查询:
SELECT * FROM PART WHERE PRICE > (SELECT PRICE FROM PART WHERE PNAME='Screw');结果是:
PNO | PNAME | PRICE -----+---------+-------- 3 | Bolt | 15 4 | Cam | 25当我们检查上面的查询时会发现出现了两次 SELECT 关键字。第一个在查询的开头 - 我们将称之为外层 SELECT - 而另一个在 WHERE 子句里面,成为一个嵌入的查询 - 我们将称之为内层 SELECT。对外层 SELECT 的每条记录都必须先计算内层 SELECT。在完成所有计算之后,我们得知名为 'Screw' 部件的记录的价格,然后我们就可以检查那些价格更贵的记录了。
如果我们想知道那些不销售任何部件的供应商(比如说,我们想把这些供应商从数据库中删除),我们用:
SELECT * FROM SUPPLIER S WHERE NOT EXISTS (SELECT * FROM SELLS SE WHERE SE.SNO = S.SNO);在我们的例子里,结果列将是空的,因为每个供应商至少销售一个部件。请注意我们在 WHERE 子句的内层 SELECT 里使用了来自外层 SELECT 的 S.SNO。正如前面所说的,子查询为每个外层查询计算一次,也就是说,S.SNO 的值总是从外层 SELECT 的实际记录中取得的。
例 69-9. Union, Intersect, Except
下面的例子是 UNION 的例子:
SELECT S.SNO, S.SNAME, S.CITY FROM SUPPLIER S WHERE S.SNAME = 'Jones' UNION SELECT S.SNO, S.SNAME, S.CITY FROM SUPPLIER S WHERE S.SNAME = 'Adams';产生结果:
SNO | SNAME | CITY -----+-------+-------- 2 | Jones | Paris 3 | Adams | Vienna下面是相交( INTERSECT)的例子:
SELECT S.SNO, S.SNAME, S.CITY FROM SUPPLIER S WHERE S.SNO > 1 INTERSECT SELECT S.SNO, S.SNAME, S.CITY FROM SUPPLIER S WHERE S.SNO > 2;产生结果:
SNO | SNAME | CITY -----+-------+-------- 2 | Jones | Paris The only tuple returned by both parts of the query is the one having $SNO=2$.(两个查询都会返回的记录是那条 $SNO=2$ 的)最后是一个 EXCEPT 的例子:
SELECT S.SNO, S.SNAME, S.CITY FROM SUPPLIER S WHERE S.SNO > 1 EXCEPT SELECT S.SNO, S.SNAME, S.CITY FROM SUPPLIER S WHERE S.SNO > 3;结果是:
SNO | SNAME | CITY -----+-------+-------- 2 | Jones | Paris 3 | Adams | Vienna
CREATE TABLE table_name (name_of_attr_1 type_of_attr_1 [, name_of_attr_2 type_of_attr_2 [, ...]]);
要创建 供应商和部件数据库 里的表,要使用下面的 SQL 语句:
CREATE TABLE SUPPLIER (SNO INTEGER, SNAME VARCHAR(20), CITY VARCHAR(20));
CREATE TABLE PART (PNO INTEGER, PNAME VARCHAR(20), PRICE DECIMAL(4 , 2));
CREATE TABLE SELLS (SNO INTEGER, PNO INTEGER);
SMALLINT: 有符号半长二进制整数(15位精度)。
DECIMAL (p[,q]): 有符号封装的十进制小数,共 p 位数,并假设有 q 位数在小数点右边。(15 ≥ p ≥ qq ≥ 0)。如果省略 q,则认为是 0。
FLOAT: 有符号双字浮点数。
CHAR(n): 长度为 n 的定长字符串。
VARCHAR(n): 最大长度为 n 的变长字符串。
在 SQL 里创建一个索引,使用 CREATE INDEX 命令。语法是:
CREATE INDEX index_name ON table_name ( name_of_attribute );
要在关系 SUPPLIER 的属性 SNAME 上创建一个名为 I 的索引,我们使用下面语法:
CREATE INDEX I ON SUPPLIER (SNAME);所创建的索引是自动维护的,也就是说,当向关系 SUPPLIER 插入新的记录时,索引 I 相应做调节。请注意有索引存在时,用户能感觉到的唯一的变化就是速度的提升。
视图没有它们自身的,物理上分开的,可区分的存储区。实际上,系统把视图的定义(也就是说,为物化试图应如何访问物理上存储在基本表中内容的规则)存放在系统表里的某个地方(参阅 系统表)。关于实现视图的不同技巧,请参考 SIM98。
在 SQL 里,使用 CREATE VIEW 命令定义视图。语法是:
CREATE VIEW view_name AS select_stmt这里 select_stmt 是一个与定义在 Select 里一样的有效选择语句。请注意在视图创建时并不执行 select_stmt。它只是存储在系统表里,当对视图进行查询时将执行。
假设给出下面的视图定义(我们再次使用来自 供应商和部件数据库 里的表):
CREATE VIEW London_Suppliers AS SELECT S.SNAME, P.PNAME FROM SUPPLIER S, PART P, SELLS SE WHERE S.SNO = SE.SNO AND P.PNO = SE.PNO AND S.CITY = 'London';现在我们可以把这个虚拟的关系 London_Suppliers 当作是另外一个基本表:
SELECT * FROM London_Suppliers WHERE P.PNAME = 'Screw';将返回下面的表:
SNAME | PNAME -------+------- Smith | Screw要计算这个结果,数据库系统必须先对基本表 SUPPLIER,SELLS 和 PART 先进行一次隐藏的访问。它是通过对这些基本表运行该视图定义里面给出的查询实现隐藏访问的。然后,余下的附加条件(在查询里给出的对视图的条件)就可以应用上来,最后获取结果表。
INSERT INTO table_name (name_of_attr_1 [, name_of_attr_2 [,...]]) VALUES (val_attr_1 [, val_attr_2 [, ...]]);要向关系 SUPPLIER 中插入第一条记录(来自 供应商和部件数据库),我们使用下面语句:
INSERT INTO SUPPLIER (SNO, SNAME, CITY) VALUES (1, 'Smith', 'London');要向关系 SELLS 插入第一条记录,我们用:
INSERT INTO SELLS (SNO, PNO) VALUES (1, 1);
我们只是希望能从用宿主语言写的应用里面访问数据库(例如,一个图形用户界面的门票预定系统可能用 C 写,而余票状况存储在一个可以用嵌入 SQL 访问的数据库里面)。
当我们观察例子 Select 时,我们认识到一个查询的结果大多是记录的集合。大多数宿主语言不是为集合而设计的,因此我们需要一个机制用来访问一条 SELECT 语句返回结果集中的每一条记录。这个机制可以通过定义一个游标来实现。之后我们就可以使用 FETCH 命令检索一条记录并把游标定义到下一条记录。
关于嵌入 SQL 的详细讨论,请参考 Date and Darwen, 1997, Date, 1994,或 Ullman, 1988。