of {$slidecount} ½ {$title} ATZJG.NET {$author}

首页






更多 SQL 介绍
Extended Relational Algebra
Outerjoins, Grouping/Aggregation
Insert/Delete/Update


Haifeng Xu


(hfxu@yzu.edu.cn)

This slide is based on Jeffrey D. Ullman's work, which can be download from his website.

目录

扩展的关系代数

扩展的关系代数

外连接算符(outerjoin operator) 是连接算符的变体, 它防止了悬浮元组(dangling tuples)的出现. (悬浮元组指不与任何其他元组连接的元组.)

消除重复的元组

消除重复的元组

$R_1:=\delta(R_2)$.

将关系 $R_2$ 中重复的元组去掉, 仅保留一个拷贝在 $R_1$ 中.

例子

$R$
A B
12
34
12
$\delta(R)$
A B
12
34
 

排序(sorting)

排序

$R_1:=\tau_{L}(R_2)$

$R_1$ 是 $R_2$ 中所有元组依次根据 $L$ 中第一、二、$\ldots$ 个属性的值进行排序所得的结果关系.

排序算子 $\tau$ 的结果关系既不是一个集合也不是一个包, 它是仅有的这样一个算子.

例子

$R$
A B
12
34
52

$\tau_{B}(R)=[(1,2),(5,2),(3,4)]$

聚合算子(Aggregation Operators)

聚集操作符(Aggregation Operators)

聚集操作符(aggregation operator)不是关系代数的操作符.

聚集操作符应用到关系的整个属性列上, 并生成单独的一个结果.

最重要的例子是: SUM, AVG, COUNT, MIN, MAX.

例子

$R$
A B
13
34
32
  SUM(A) = 7
COUNT(A) = 3
  MAX(B) = 4
  AVG(B) = 3

实验

mysql> select count(*) from bars;
+----------+
| count(*) |
+----------+
|        7 |
+----------+
1 row in set (0.00 sec)
mysql> insert into bars (addr, license) values ("文昌路1","test1");
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select count(*) from bars;
+----------+
| count(*) |
+----------+
|        8 |
+----------+
1 row in set (0.01 sec)

mysql> select count(name) from bars;
+-------------+
| count(name) |
+-------------+
|           8 |
+-------------+
1 row in set (0.00 sec)
mysql> show create table bars\G;
*************************** 1. row ***************************
       Table: bars
Create Table: CREATE TABLE `bars` (
  `name` varchar(20) NOT NULL,
  `addr` varchar(255) DEFAULT NULL,
  `license` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk
1 row in set (0.00 sec)


mysql> insert into bars values (NULL, "文昌路2","test2");
ERROR 1048 (23000): Column 'name' cannot be null
mysql> create table bars2(
    -> name varchar(20),
    -> addr varchar(255),
    -> license varchar(255)
    -> )ENGINE=InnoDB DEFAULT CHARSET=gbk;
Query OK, 0 rows affected (0.03 sec)

mysql> insert into bars2 values ("WestSide1", "文昌路1","test1");
Query OK, 1 row affected (0.00 sec)

mysql> insert into bars2 values (NULL, "文昌路2","test2");
Query OK, 1 row affected (0.00 sec)
mysql> select count(*) from bars2;
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

mysql> select count(name) from bars2;
+-------------+
| count(name) |
+-------------+
|           1 |
+-------------+
1 row in set (0.00 sec)
 

分组操作符(Grouping Operator)

分组操作符(Grouping Operator)

$R_1:=\gamma_L(R_2)$. 其中 $L$ 是下面情形之一:

  1. $L$ 是关系 $R_2$ 的一个属性, $R_2$ 使用这个属性进行分组. 因此该属性称为分组属性(grouping attribute).
  2. $L=AGG(A)$, 这里 $AGG$ 是某个聚集操作符(如: SUM, AVG, COUNT, MIN, MAX), $A$ 是某个属性.
    • 为了在结果中给该聚集一个属性名称, 使用一个箭头和一个新的名字附加在这个聚集的后面.

应用 $\gamma_L(R)$

应用 $\gamma_L(R)$

根据 $L$ 中的分组属性将关系 R 进行分组.

在每个组内, 对于列表 $L$ 的聚集属性, 计算 $AGG(A)$.

对于每一组, 产生如下内容的元组:

  1. 那个组的分组属性值,
  2. 组的聚集操作的结果.

例子: 分组/聚集

R
A B C
123
456
125

$\gamma_{A,B,AVG(C)\rightarrow X}(R)=??$

首先, 对于关系 R, 根据属性 A, B 进行分组:

R
A B C
123
125
456

然后, 在每个组内, 对于属性列 $C$, 进行求平均值的聚集操作.

A B X
124
456
 

外连接(Outerjoin)

外连接(Outerjoin)

假设我们要进行($\theta$-)连接: $R\bowtie_C S$.

R 中某个元组如果找不到 S 中元组与其连接, 则称之为悬浮的(dangling).

外连接(Outerjoin) $R\stackrel{\circ}{\bowtie}S$ 首先进行的操作是自然连接 $R\bowtie S$, 然后再把来自 R 或 S 的悬浮元组加入其中. 加入的元组用 null 符号 $\perp$ 补齐那些出现在结果中但不具有值的属性.

例子: 外连接(Outerjoin)

R
A B
12
45
B C
23
67
 

(1,2)(2,3) 连接, 但另外两个元组是悬浮的.

R OUTERJOIN S
A B C
123
45NULL
NULL67

现在 --- 回到 SQL

现在 --- 回到 SQL

每个(扩展的)关系代数表达式都对应到一个与之等价的 SQL 语句.

外连接(Outerjoin)

外连接(Outerjoin)

R OUTER JOIN S 是外连接表达式的核心. 它可以修改为:

  1. OUTER 之前加 NATURAL.
  2. JOIN 之后加 ON <condition>.(这两者只能二选一.)
  3. OUTER 前面加 LEFT, RIGHTFULL.
    • LEFT = 只对 R 的悬浮元组进行填充.
    • RIGHT = 只对 S 的悬浮元组进行填充.
    • FULL = 对 R 和 S 的悬浮元组都进行填充.

mysql> select * from R natural left OUTER JOIN S;
+------+------+------+
| B    | A    | C    |
+------+------+------+
|    2 |    1 |    3 |
|    5 |    4 | NULL |
+------+------+------+
2 rows in set (0.00 sec)

MySQL 中 JOIN 的语法

MySQL supports the following JOIN syntaxes for the table_references part of SELECT statements and multiple-table DE- LETE and UPDATE statements:

table_references:
table_reference, table_reference
| table_reference [INNER | CROSS] JOIN table_reference [join_condition]
| table_reference STRAIGHT_JOIN table_reference
| table_reference LEFT [OUTER] JOIN table_reference join_condition
| table_reference NATURAL [LEFT [OUTER]] JOIN table_reference
| { OJ table_reference LEFT OUTER JOIN table_reference
ON conditional_expr }
| table_reference RIGHT [OUTER] JOIN table_reference join_condition
| table_reference NATURAL [RIGHT [OUTER]] JOIN table_reference
table_reference:
tbl_name [[AS] alias] [index_hint)]
join_condition:
ON conditional_expr
| USING (column_list)
index_hint:
USE {INDEX|KEY} (index_list)]
| IGNORE {INDEX|KEY} (index_list)]
| FORCE {INDEX|KEY} (index_list)]
index_list:
index_name [, index_name] ...

聚集操作(Aggregations)

聚集操作(Aggregations)

SUM, AVG, COUNT, MIN, 和 MAX 可以应用到 SELECT 语句中的某个列, 对于该列上的数据进行聚集操作.

COUNT(*) 用于计算元组的个数.

例子: Aggregation

对于关系 Sells(bar, beer, price), 求 Bud 啤酒的平均价格.

SELECT AVG(price)
FROM Sells
WHERE beer='Bud';

实验

mysql> SELECT AVG(price) 
    -> FROM Sells 
    -> WHERE beer='百威';
+--------------------+
| AVG(price)         |
+--------------------+
| 31.666666666666668 |
+--------------------+
1 row in set (0.00 sec)

检验一下

mysql> SELECT price FROM Sells WHERE beer='百威';
+-------+
| price |
+-------+
|    30 |
|    32 |
|    32 |
|    32 |
|    32 |
|    32 |
+-------+
6 rows in set (0.00 sec)
mysql> select (30+32*5)/6;
+-------------+
| (30+32*5)/6 |
+-------------+
|     31.6667 |
+-------------+
1 row in set (0.00 sec)

在聚集操作中消除重复的元组

在聚集操作中消除重复的元组

在聚集操作中加入 DISTINCT.

例如对于关系 Sells(bar, beer, price)

例子: 对于关系 Sells(bar, beer, price), 求 Bud 啤酒不同售价的个数.

SELECT COUNT(DISTINCT price)
FROM Sells
WHERE beer='Bud';

实验

mysql> SELECT COUNT(DISTINCT price)
    -> FROM Sells
    -> WHERE beer='贝克';
+-----------------------+
| COUNT(DISTINCT price) |
+-----------------------+
|                     4 |
+-----------------------+
1 row in set (0.00 sec)

NULL 在聚集操作中是被忽略的

NULL 在聚集操作中是被忽略的

NULL 在求和、求平均、计数操作上都不做贡献, 且不会被认为是该列的最小或最大值.

但如果某个列没有非NULL值, 则对该列进行聚集操作的结果是 NULL.

例子: NULL 的影响

SELECT count(*)
FROM Sells
WHERE beer='Bud';

返回销售 Bud 啤酒的酒吧个数.

SELECT count(price)
FROM Sells
WHERE beer='Bud';

返回销售 Bud 啤酒并且有明确售价的酒吧个数.

实验

mysql> SELECT count(*)
    -> FROM Sells
    -> WHERE beer='贝克';
+----------+
| count(*) |
+----------+
|        5 |
+----------+
1 row in set (0.00 sec)
mysql> SELECT count(price) 
    -> FROM Sells 
    -> WHERE beer='贝克';
+--------------+
| count(price) |
+--------------+
|            5 |
+--------------+
1 row in set (0.00 sec)

这说明贝克啤酒都有明确售价. 现在加入一条记录到表 Sells 中, 但是没有价格.

mysql> INSERT INTO Sells (bar,beer)
    -> VALUES ("宝莱纳餐厅","贝克");

再执行上面的查询就会发现不同了.

分组(Grouping)

分组(Grouping)

SELECT-FROM-WHERE 语句后面可以跟 GROUP BY 和一列属性.

SELECT-FROM-WHERE 语句所得到的关系是根据所列出的属性的值进行分组的. 分组后才进行聚集操作, 任何聚集操作也仅是在每个组内进行的.

例子: 分组(Grouping)

对于关系 Sells(bar, beer, price), 对所销售的每种啤酒计算平均售价.

SELECT beer, AVG(price)
FROM Sells
GROUP BY beer;
beer AVG(price)
Bud 2.33
... ...

例2: 分组(Grouping)

对于关系 Sells(bar, beer, price)Frequents(drinker, bar), 计算每个酒客经常去的酒吧所销售 Bud 啤酒的均价.

SELECT drinker, AVG(price)
FROM Frequents, Sells
WHERE beer='Bud' AND
	Frequents.bar=Sells.bar
GROUP BY drinker;

上面使用了 $\theta$-连接, 也可以改用自然连接.

SELECT drinker, AVG(price)
FROM Frequents NATURAL JOIN Sells
WHERE beer='Bud' 
GROUP BY drinker;

实验

mysql> SELECT beer, AVG(price)
    -> FROM Sells
    -> GROUP BY beer;
+-----------------+--------------------+
| beer            | AVG(price)         |
+-----------------+--------------------+
| Stella Artois   |                 36 |
| 健力士黑啤      |               36.4 |
| 南非啤酒        |                 36 |
| 喜力            |               28.8 |
| 嘉士伯          |                 35 |
| 安贝夫          |              39.75 |
| 朝日            |                 26 |
| 生力            |               31.5 |
| 百威            | 31.666666666666668 |
| 科罗娜          |                 40 |
| 米勒            |                 41 |
| 纯种苦啤酒      |                 33 |
| 贝克            |               34.6 |
| 麒麟            |               34.5 |
+-----------------+--------------------+
14 rows in set (0.00 sec)
mysql> SELECT drinker, AVG(price) 
    -> FROM Frequents, Sells 
    -> WHERE beer='贝克' AND 
    -> Frequents.bar=Sells.bar 
    -> GROUP BY drinker;
+---------------------+------------+
| drinker             | AVG(price) |
+---------------------+------------+
| Alan Turing         |         33 |
| Andrew Chi-Chih Yao |       NULL |
| Charles Babbage     |         35 |
| Claude Shannon      |         35 |
| David Patterson     |         42 |
| Donald Ervin Knuth  |         35 |
| Edsger Dijkstra     |         33 |
| Gerald Jay Sussman  |       NULL |
| Guy Steele Jr.      |         35 |
| atzjg             |         28 |
| Herbert A. Simon    |         28 |
| John Hennessy       |         35 |
| John von Neumann    |         42 |
| Lynn Conway         |         28 |
| Tim Berners-Lee     |         33 |
| Tony Hoare          |       NULL |
+---------------------+------------+
16 rows in set (0.00 sec)

也可以使用 Natural Join

mysql-> select drinker, AVG(price) 
     -> FROM Frequents NATURAL JOIN Sells 
     -> WHERE beer='贝克' 
     -> GROUP BY drinker;

如果不加上 GROUP BY drinker, 会怎样?

mysql> SELECT drinker, AVG(price) 
    -> FROM Frequents, Sells 
    -> WHERE beer='贝克' AND 
    -> Frequents.bar=Sells.bar;
+-------------+------------+
| drinker     | AVG(price) |
+-------------+------------+
| Alan Turing |         34 |
+-------------+------------+
1 row in set (0.00 sec)

它计算的是酒客经常去的有贝克啤酒出售的酒吧关于贝克的平均售价.

mysql> SELECT avg(price) FROM Frequents,Sells
    -> WHERE beer="贝克" AND Frequents.bar=Sells.bar;
+------------+
| avg(price) |
+------------+
|         34 |
+------------+
1 row in set (0.00 sec)

如果执行下面的命令, 会怎样?

mysql> SELECT drinker FROM Frequents,Sells 
    -> WHERE beer="贝克" 
    -> AND Frequents.bar=Sells.bar;
+---------------------+
| drinker             |
+---------------------+
| Alan Turing         |
| Andrew Chi-Chih Yao |
| Charles Babbage     |
| Claude Shannon      |
| David Patterson     |
| Donald Ervin Knuth  |
| Edsger Dijkstra     |
| Gerald Jay Sussman  |
| Guy Steele Jr.      |
| atzjg             |
| atzjg             |
| Herbert A. Simon    |
| John Hennessy       |
| John von Neumann    |
| Lynn Conway         |
| Tim Berners-Lee     |
| Tony Hoare          |
+---------------------+
17 rows in set (0.00 sec)

发现多了一条记录, 是用户 atzjg 的. 事实上, 这是因为在原始数据中用户 atzjg 经常去的宝莱纳餐厅并不销售贝克啤酒, 后来我们插入了一条记录:

mysql> INSERT INTO Sells (bar,beer)
    -> VALUES ("宝莱纳餐厅","贝克");

使得其价格属性值为 NULL. 但是这并不影响 atzjg 符合该 SQL 语句的查询条件, 因此也在结果之中.

含有聚集操作的 SELECT 列表上的限制

含有聚集操作的 SELECT 列表上的限制

在 SELECT 语句中如果使用了聚集操作, 则 SELECT 列表中的每个元素只能是下列两种情形之一:

  1. 聚集操作之结果(Aggregated),
  2. GROUP BY 列表中的属性.

不合语法的例子

你也许会认为可以根据下面的查询语句找到 Bud 啤酒卖得最便宜的酒吧:

SELECT bar, MIN(price)
FROM Sells
WHERE beer='Bud';

但是这个查询在 SQL 中是不合法的.

实验

mysql> SELECT bar, MIN(price)
    -> FROM Sells
    -> WHERE beer='贝克';
+----------+------------+
| bar      | MIN(price) |
+----------+------------+
| 3DArtBar |         28 |
+----------+------------+
1 row in set (0.00 sec)

看上去在 MySQL 中是可以运行的, 但 3DArtBar 是正确答案吗?

mysql> select * from Sells where beer="贝克";
+--------------------------+--------+-------+
| bar                      | beer   | price |
+--------------------------+--------+-------+
| 3DArtBar                 | 贝克   |    35 |
| HardRock                 | 贝克   |    33 |
| Westside                 | 贝克   |    28 |
| 宝莱纳餐厅               | 贝克   |  NULL |
| 扬州老啤酒厂酒吧         | 贝克   |    42 |
| 木板房啤酒吧             | 贝克   |    35 |
+--------------------------+--------+-------+
6 rows in set (0.00 sec)

可见在进行聚集操作后, bar 属性返回的是第一条记录, 而不是与最小价格所对应的 Westside.

如果查询价格最低的啤酒信息, 则可以使用下面的语句.

mysql> SELECT * FROM Sells 
    -> WHERE price <= all
    -> (SELECT price FROM Sells);

但是现在要查询的是卖贝克啤酒最便宜的酒吧. 因此要缩小查询的范围.

mysql> SELECT * FROM 
    -> (SELECT * FROM Sells WHERE beer="贝克") AS Beike 
    -> WHERE price <= all
    -> (SELECT price FROM Sells WHERE beer="贝克");
+----------+--------+-------+
| bar      | beer   | price |
+----------+--------+-------+
| Westside | 贝克   |    28 |
+----------+--------+-------+
1 row in set (0.00 sec)

HAVING 语句

HAVING 语句

HAVING <condition> 可以跟在 GROUP BY 子句后面.

如果加了 HAVING <condition>, 则这个条件将应用于每个组, 不满足条件的组将被消除.

例子: HAVING 语句

对于关系 Sells(bar, beer, price)Beers(name, manf), 对于那些供应至少三个酒吧, 或者是由 Pete's 生产的啤酒, 计算它们的平均价格.

解决方案

SELECT beer, AVG(price)
FROM Sells
GROUP BY beer
HAVING COUNT(bar)>=3 OR
	beer IN (SELECT name
		FROM Beers
		WHERE manf='Peter''s');

实验

mysql> SELECT beer, AVG(price)
    -> FROM Sells
    -> GROUP BY beer
    -> HAVING COUNT(bar)>=3 OR
    -> beer IN (SELECT name
    -> FROM Beers
    -> WHERE manf='百威英博啤酒集团');
+-----------------+--------------------+
| beer            | AVG(price)         |
+-----------------+--------------------+
| Stella Artois   |                 36 |
| 健力士黑啤      |               36.4 |
| 南非啤酒        |                 36 |
| 喜力            |               28.8 |
| 嘉士伯          |                 35 |
| 安贝夫          |              39.75 |
| 朝日            |                 26 |
| 百威            | 31.666666666666668 |
| 科罗娜          |                 40 |
| 纯种苦啤酒      |                 33 |
| 贝克            |               34.6 |
+-----------------+--------------------+
11 rows in set (0.00 sec)

不妨分别查看一下分属上面两个条件的记录:

mysql> SELECT beer, AVG(price) 
    -> FROM Sells 
    -> GROUP BY beer 
    -> HAVING COUNT(bar)>=3;
+-----------------+--------------------+
| beer            | AVG(price)         |
+-----------------+--------------------+
| 健力士黑啤      |               36.4 |
| 南非啤酒        |                 36 |
| 喜力            |               28.8 |
| 嘉士伯          |                 35 |
| 安贝夫          |              39.75 |
| 朝日            |                 26 |
| 百威            | 31.666666666666668 |
| 科罗娜          |                 40 |
| 纯种苦啤酒      |                 33 |
| 贝克            |               34.6 |
+-----------------+--------------------+
10 rows in set (0.00 sec)
mysql> SELECT beer, AVG(price) 
    -> FROM Sells 
    -> GROUP BY beer 
    -> HAVING  beer IN 
    -> (SELECT name FROM Beers 
    ->  WHERE manf='百威英博啤酒集团');
+---------------+--------------------+
| beer          | AVG(price)         |
+---------------+--------------------+
| Stella Artois |                 36 |
| 百威          | 31.666666666666668 |
| 贝克          |               34.6 |
+---------------+--------------------+
3 rows in set (0.00 sec)

想一想下面的语句会返回什么? 为什么?

mysql> SELECT beer, AVG(price)
    -> FROM Sells 
    -> HAVING  beer IN 
    -> (SELECT name FROM Beers 
    ->  WHERE manf='百威英博啤酒集团');
Empty set (0.00 sec)

此时默认只有一个记录, 并且此时返回的 beer 肯定不是“百威英博啤酒集团”生产的.

mysql> SELECT beer, AVG(price) FROM Sells;
+--------+------------+
| beer   | AVG(price) |
+--------+------------+
| 喜力   |      34.42 |
+--------+------------+
1 row in set (0.00 sec)

关于 HAVING 子句中 Condition 的要求

子查询中可以是任意的语句

在子查询外, 属性只能是下面之一:

(与含有聚集操作的 SELECT 子句是同样的要求.)

实验(under MySQL)

对于 MySQL 来说, 这些语句可以运行, 但是可能有时不是你期望的. 对于下面的语句, 指出所得结果数据的含义. 特别是为什么它们的 AVG(price) 不一致?

mysql> SELECT beer,bar, AVG(price)
    -> FROM Sells 
    -> GROUP BY beer 
    -> HAVING  beer IN 
    -> (SELECT name FROM Beers 
    ->  WHERE manf = '喜利得(中国)有限公司');
+--------+----------+------------+
| beer   | bar      | AVG(price) |
+--------+----------+------------+
| 喜力   | 3DArtBar |       28.8 |
+--------+----------+------------+
1 row in set (0.01 sec)
mysql> SELECT beer,bar, AVG(price) 
    -> FROM Sells 
    -> HAVING  beer IN 
    -> (SELECT name FROM Beers 
    ->  WHERE manf='喜利得(中国)有限公司');
+--------+----------+------------+
| beer   | bar      | AVG(price) |
+--------+----------+------------+
| 喜力   | 3DArtBar |      34.42 |
+--------+----------+------------+
1 row in set (0.00 sec)

上面的语句没有 GROUP BY, 此时默认是一组. 而且接下来的 HAVING 语句总是成立的. 因此上面的语句等价于 SELECT beer,bar,AVG(price)FROM Sells; 因此计算的是所有啤酒的平均售价. 我们不妨检验一下.

mysql> select count(*), SUM(price) from Sells;
+----------+------------+
| count(*) | SUM(price) |
+----------+------------+
|       51 |       1721 |
+----------+------------+
1 row in set (0.00 sec)

mysql> select 1721/51;
+---------+
| 1721/51 |
+---------+
| 33.7451 |
+---------+
1 row in set (0.00 sec)

别忘了, 我们曾经插入一条记录: 某个酒吧销售某种啤酒但没有售价.

mysql> select SUM(price),count(price), SUM(price)/count(price) AS AVGPrice from Sells;
+------------+--------------+----------+
| SUM(price) | count(price) | AVGPrice |
+------------+--------------+----------+
|       1721 |           50 |    34.42 |
+------------+--------------+----------+
1 row in set (0.00 sec)

mysql> SELECT beer,bar, AVG(price) 
    -> FROM Sells 
    -> GROUP BY bar 
    -> HAVING beer IN 
    -> (SELECT name FROM Beers 
    ->  WHERE manf LIKE '%公司%');
+-----------------+-----------------+-------------------+
| beer            | bar             | AVG(price)        |
+-----------------+-----------------+-------------------+
| 喜力            | 3DArtBar        |              32.4 |
| 健力士黑啤      | HardRock        | 34.42857142857143 |
| 健力士黑啤      | Westside        | 33.42857142857143 |
| 健力士黑啤      | 宝莱纳餐厅      | 33.42857142857143 |
| 健力士黑啤      | 苏荷酒吧        |                34 |
+-----------------+-----------------+-------------------+
5 rows in set (0.01 sec)
mysql> SELECT beer,bar, AVG(price)
    -> FROM Sells 
    -> GROUP BY beer 
    -> HAVING  beer IN 
    -> (SELECT name FROM Beers 
    ->  WHERE manf like '%公司%');
+-----------------+--------------------------+------------+
| beer            | bar                      | AVG(price) |
+-----------------+--------------------------+------------+
| 健力士黑啤      | HardRock                 |       36.4 |
| 喜力            | 3DArtBar                 |       28.8 |
| 安贝夫          | 3DArtBar                 |      39.75 |
| 生力            | 木板房啤酒吧             |       31.5 |
| 科罗娜          | HardRock                 |         40 |
| 米勒            | 扬州老啤酒厂酒吧         |         41 |
| 纯种苦啤酒      | Westside                 |         33 |
| 麒麟            | 扬州老啤酒厂酒吧         |       34.5 |
+-----------------+--------------------------+------------+
8 rows in set (0.00 sec)

继续做实验, 然后分析是怎么回事

mysql> select beer,bar,AVG(price)
    -> from Sells
    -> group by bar;
+-----------------+--------------------------+--------------------+
| beer            | bar                      | AVG(price)         |
+-----------------+--------------------------+--------------------+
| 喜力            | 3DArtBar                 |               32.4 |
| 健力士黑啤      | HardRock                 |  34.42857142857143 |
| 健力士黑啤      | Westside                 |  33.42857142857143 |
| 健力士黑啤      | 宝莱纳餐厅               |  33.42857142857143 |
| Stella Artois   | 扬州老啤酒厂酒吧         |               37.3 |
| 嘉士伯          | 木板房啤酒吧             | 34.166666666666664 |
| 健力士黑啤      | 苏荷酒吧                 |                 34 |
+-----------------+--------------------------+--------------------+
7 rows in set (0.00 sec)
mysql> select beer,bar,AVG(price) from Sells group by beer;
+-----------------+--------------------------+--------------------+
| beer            | bar                      | AVG(price)         |
+-----------------+--------------------------+--------------------+
| Stella Artois   | 扬州老啤酒厂酒吧         |                 36 |
| 健力士黑啤      | HardRock                 |               36.4 |
| 南非啤酒        | Westside                 |                 36 |
| 喜力            | 3DArtBar                 |               28.8 |
| 嘉士伯          | 3DArtBar                 |                 35 |
| 安贝夫          | 3DArtBar                 |              39.75 |
| 朝日            | Westside                 |                 26 |
| 生力            | 木板房啤酒吧             |               31.5 |
| 百威            | 3DArtBar                 | 31.666666666666668 |
| 科罗娜          | HardRock                 |                 40 |
| 米勒            | 扬州老啤酒厂酒吧         |                 41 |
| 纯种苦啤酒      | Westside                 |                 33 |
| 贝克            | 3DArtBar                 |               34.6 |
| 麒麟            | 扬州老啤酒厂酒吧         |               34.5 |
+-----------------+--------------------------+--------------------+
14 rows in set (0.00 sec)

Sells 表中有 51 条记录, 共有 7 个酒吧.

mysql> select beer,bar,price from Sells;
+-----------------+--------------------------+-------+
| beer            | bar                      | price |
+-----------------+--------------------------+-------+
| 喜力            | 3DArtBar                 |    25 |
| 嘉士伯          | 3DArtBar                 |    32 |
| 安贝夫          | 3DArtBar                 |    40 |
| 百威            | 3DArtBar                 |    30 |
| 贝克            | 3DArtBar                 |    35 |
| 健力士黑啤      | HardRock                 |    39 |
| 喜力            | HardRock                 |    28 |
| 嘉士伯          | HardRock                 |    30 |
| 安贝夫          | HardRock                 |    39 |
| 百威            | HardRock                 |    32 |
| 科罗娜          | HardRock                 |    40 |
| 贝克            | HardRock                 |    33 |
| 健力士黑啤      | Westside                 |    39 |
| 南非啤酒        | Westside                 |    36 |
| 朝日            | Westside                 |    26 |
| 百威            | Westside                 |    32 |
| 科罗娜          | Westside                 |    40 |
| 纯种苦啤酒      | Westside                 |    33 |
| 贝克            | Westside                 |    28 |
| 健力士黑啤      | 宝莱纳餐厅               |    39 |
| 南非啤酒        | 宝莱纳餐厅               |    36 |
| 喜力            | 宝莱纳餐厅               |    28 |
| 朝日            | 宝莱纳餐厅               |    26 |
| 百威            | 宝莱纳餐厅               |    32 |
| 科罗娜          | 宝莱纳餐厅               |    40 |
| 纯种苦啤酒      | 宝莱纳餐厅               |    33 |
| 贝克            | 宝莱纳餐厅               |  NULL |
| Stella Artois   | 扬州老啤酒厂酒吧         |    36 |
| 健力士黑啤      | 扬州老啤酒厂酒吧         |    26 |
| 喜力            | 扬州老啤酒厂酒吧         |    35 |
| 安贝夫          | 扬州老啤酒厂酒吧         |    40 |
| 百威            | 扬州老啤酒厂酒吧         |    32 |
| 科罗娜          | 扬州老啤酒厂酒吧         |    40 |
| 米勒            | 扬州老啤酒厂酒吧         |    50 |
| 纯种苦啤酒      | 扬州老啤酒厂酒吧         |    33 |
| 贝克            | 扬州老啤酒厂酒吧         |    42 |
| 麒麟            | 扬州老啤酒厂酒吧         |    39 |
| 嘉士伯          | 木板房啤酒吧             |    43 |
| 安贝夫          | 木板房啤酒吧             |    40 |
| 生力            | 木板房啤酒吧             |    25 |
| 米勒            | 木板房啤酒吧             |    32 |
| 贝克            | 木板房啤酒吧             |    35 |
| 麒麟            | 木板房啤酒吧             |    30 |
| 健力士黑啤      | 苏荷酒吧                 |    39 |
| 南非啤酒        | 苏荷酒吧                 |    36 |
| 喜力            | 苏荷酒吧                 |    28 |
| 朝日            | 苏荷酒吧                 |    26 |
| 生力            | 苏荷酒吧                 |    38 |
| 百威            | 苏荷酒吧                 |    32 |
| 科罗娜          | 苏荷酒吧                 |    40 |
| 纯种苦啤酒      | 苏荷酒吧                 |    33 |
+-----------------+--------------------------+-------+
51 rows in set (0.00 sec)

如果 SELECT 后面跟的属性不符合要求, 比如 beer, 则若根据 bar 分组, 则相应的 beer 处的值是该组中的第一个属性值.

mysql> SELECT beer,bar,price 
    -> FROM Sells 
    -> GROUP BY bar;
+-----------------+--------------------------+-------+
| beer            | bar                      | price |
+-----------------+--------------------------+-------+
| 喜力            | 3DArtBar                 |    25 |
| 健力士黑啤      | HardRock                 |    39 |
| 健力士黑啤      | Westside                 |    39 |
| 健力士黑啤      | 宝莱纳餐厅               |    39 |
| Stella Artois   | 扬州老啤酒厂酒吧         |    36 |
| 嘉士伯          | 木板房啤酒吧             |    43 |
| 健力士黑啤      | 苏荷酒吧                 |    39 |
+-----------------+--------------------------+-------+
7 rows in set (0.00 sec)

数据库的更新(modification)

数据库的更新(modification)

与查询不同, 更新命令不返回结果, 但以某种方式改变数据库.

三种更新操作:

  1. Insert, 插入一个或多个元组.
  2. Delete, 删除一个或多个元组.
  3. Update, 更新(改变)已存在的一个或多个元组的值.

插入(Insertion)

插入(Insertion)

插入单个元组:

INSERT INTO <relation>
VALUES (<list of values>);

例子: 对于关系 Likes(drinker, beer), 增加 Sally 喜欢和 Bud 啤酒.

INSERT INTO Likes
VALUES ('Sally', 'Bud');

在 INSERT 语句中指定要插入元组的属性

在 INSERT 语句中指定要插入元组的属性

可以在要插入记录的关系名后面加入一列属性名.

这样做有两个原因:

  1. 我们忘记了关系中属性的标准次序(就是定义时的次序)是什么了.
  2. 我们并没有准备对所有属性赋值(或者某些属性确实我们还没有值), 我们希望系统对于未列出的属性自动填充空值(NULL)或定义时指定的默认值.

例子: 指定属性的插入

下面是另一种方式, 将 Sally 喜欢 Bud 啤酒这一事实添加到关系 Likes(drinker, beer) 中.

INSERT INTO Likes(beer, drinker)
VALUES ('Bud', 'Sally');

MySQL 中提供了一次插入多个元组的方式.

INSERT INTO Likes(beer, drinker)
VALUES ('Bud', 'Sally'), ('Bud2', 'Sally2'), ('Bud3', 'Sally3');

添加默认值

添加默认值

CREATE TABLE 语句中, 在一个属性后面添加 DEFAULT 和一个值.

当要插入的元组在某个属性上没有值时, 缺省值(default value) 就派上用场了.

例子 1: 默认值/缺省值

CREATE TABLE Drinkers(
	name CHAR(30) PRIMARY KEY,
	addr CHAR(50) DEFAULT '123 Sesame St.',
	phone CHAR(16)
);

例子 2: 默认值/缺省值

INSERT INTO Drinkers(name)
VALUES('Sally');

所得到的元组

name address phone
Sally 123 Sesame St. NULL

实验

CREATE TABLE PotBuddies(
	name CHAR(30) PRIMARY KEY,
	friend_of CHAR(50) DEFAULT 'atzjg'
);
INSERT INTO PotBuddies(name)
VALUES('Sally');

一次插入多个元组

一次插入多个元组

我们可以将某个查询的整个结果插入到某个关系中, 利用下面的形式:

INSERT INTO <relation>
(<subquery>);

例子: 插入一个子查询

利用关系 Frequents(drinker, bar), 将 Sally 的所有“潜在哥们”插入到新关系 PotBuddies(name,friend_of) 中去. 这里 Sally 潜在的哥们是指那些至少也去过 Sally 经常去的酒吧的人.

解决方案

INSERT INTO PotBuddies(name)
(SELECT d2.drinker
FROM Frequents d1, Frequents d2
WHERE d1.drinker='Sally' AND
  d2.drinker <> 'Sally' AND
  d1.bar = d2.bar
);

d2.drinker 是指另一位酒客. FROM ... WHERE ... 找出的即是一对酒客, 其中第一位是 Sally, 另一位其他人, 他也经常去同一酒吧喝酒.

实验

mysql> INSERT INTO PotBuddies(name)
    -> (SELECT d2.drinker
    -> FROM Frequents d1, Frequents d2
    -> WHERE d1.drinker='atzjg' AND
    ->   d2.drinker <> 'atzjg' AND
    ->   d1.bar = d2.bar
    -> );
Query OK, 5 rows affected (0.42 sec)
Records: 5  Duplicates: 0  Warnings: 0
mysql> select * from PotBuddies;
+---------------------+-----------+
| name                | friend_of |
+---------------------+-----------+
| Andrew Chi-Chih Yao | atzjg     |
| Gerald Jay Sussman  | atzjg     |
| Herbert A. Simon    | atzjg     |
| Lynn Conway         | atzjg     |
| Sally               | atzjg     |
| Tony Hoare          | atzjg     |
+---------------------+-----------+
6 rows in set (0.00 sec)

删除(Deletion)

删除(Deletion)

从某个关系中删除满足某种条件的元组, 使用下面的语句:

DELETE FROM <relation>
WHERE <condition>;

例子: 删除某个元组

从关系 Likes(drinker, beer) 中删除 Sally 喜欢喝 Bud 啤酒这一记录:

DELETE FROM Likes
WHERE drinker='Sally' 
	AND beer='Bud';

例子: 删除所有元组

将关系 Likes(drinker, beer) 中的所有元组都删除.

DELETE FROM Likes;

注意不需要 WHERE 子句.

例子: 删除某些元组

从关系 Beers(name, manf) 中删除这样的记录, 存在由同一厂家生产的其他牌子的啤酒.

DELETE FROM Beers b
WHERE EXISTS (
	SELECT name FROM Beers
	WHERE manf=b.manf AND
		name <> b.name);

子查询返回的结果关系是那些是同一厂家生产但与元组 b (中的啤酒)不同名的啤酒. 只要这个结果关系非空, 则删除这样的元组 b.

实验

mysql> DELETE FROM PotBuddies
    -> WHERE name ='Sally';

在做上面最后一个实验前, 先做一些准备. 比如搞清楚要删除的是什么?

mysql> SELECT * FROM Beers b
    -> WHERE EXISTS (
    -> SELECT name FROM Beers
    -> WHERE manf=b.manf AND
    -> name <> b.name);
+---------------+--------------------------+
| name          | manf                     |
+---------------+--------------------------+
| 百威          | 百威英博啤酒集团         |
| 贝克          | 百威英博啤酒集团         |
| Stella Artois | 百威英博啤酒集团         |
+---------------+--------------------------+
3 rows in set (0.35 sec)

结果表明只有百威、贝克和 Stella Artois 是同一厂家生产的. 你可以使用 SELECT * FROM Beers; 来查看是否属实. 下面我们把它们从记录中删除. 当然删除后, 不妨仍然将它们插入到 Beers 表中. 我们这里只是做实验.

如果将上面的 <> 改为 <, 发现只有

+---------------+--------------------------+
| name          | manf                     |
+---------------+--------------------------+
| 百威          | 百威英博啤酒集团         |
| 贝克          | 百威英博啤酒集团         |
+---------------+--------------------------+

看看下面的语句会发生什么?

mysql> SELECT * FROM Beers b
    -> WHERE b.name in (
    -> SELECT name FROM Beers
    -> WHERE manf=b.manf AND
    -> name <> b.name);
Empty set (0.00 sec)

正确的应该是

mysql> select * from Beers b 
    -> where b.name in ( 
    ->   select Beers.name from Beers, Beers as b 
    ->   where Beers.manf=b.manf  
    ->   and Beers.name <> b.name);
+---------------+--------------------------+
| name          | manf                     |
+---------------+--------------------------+
| 百威          | 百威英博啤酒集团         |
| 贝克          | 百威英博啤酒集团         |
| Stella Artois | 百威英博啤酒集团         |
+---------------+--------------------------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM Beers 
    -> WHERE name in (
    -> SELECT name FROM Beers AS b
    -> WHERE manf=b.manf AND
    -> name <> b.name);
mysql> SELECT * FROM Beers 
    -> WHERE EXISTS (
    -> SELECT name FROM Beers AS b
    -> WHERE manf=b.manf AND
    -> name <> b.name);
mysql> DELETE FROM Beers b
    -> WHERE EXISTS (
    -> SELECT name FROM Beers
    -> WHERE manf=b.manf AND
    -> name <> b.name);
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'b
WHERE EXISTS (
SELECT name FROM Beers
WHERE manf=b.manf AND
name <> b.name)' at line 1

这样的语句在 MySQL 中不能运行. 我们得更改以下. 下面的正确吗?

mysql> DELETE FROM Beers 
    -> WHERE EXISTS (
    -> SELECT name FROM Beers AS b 
    -> WHERE b.manf=manf AND
    -> b.name <> name);
ERROR 1093 (HY000): You can't specify target table 'Beers' for update in FROM clause    

正确的应该是:

mysql> SELECT * FROM Beers
    -> WHERE name IN (
    ->  SELECT B.name FROM ( 
    ->     SELECT B0.name, B0.manf FROM Beers AS B0, Beers AS B1
    ->     WHERE B1.name <> B0.name AND
    ->     B1.manf=B0.manf
    ->     )B );
mysql> DELETE FROM Beers
    -> WHERE name IN (
    ->  SELECT B.name FROM ( 
    ->     SELECT B0.name, B0.manf FROM Beers AS B0, Beers AS B1
    ->     WHERE B1.name <> B0.name AND
    ->     B1.manf=B0.manf
    ->     )B );

下面我将删除的数据重新加入到数据库中.

mysql> INSERT INTO Beers VALUES
    -> ('百威','百威英博啤酒集团'),
    -> ('贝克','百威英博啤酒集团'),
    -> ('Stella Artois','百威英博啤酒集团');

注意上面一下子插入三条记录的方式是 MySQL 所特有的.

注意 VALUES 可以改为 VALUE

Semantics of Deletion

Semantics of Deletion ---(1)

假设 安海斯-布希公司(Anheuser-Busch) 只生产 BudBud Lite.

假设变量 t 首先碰到的是 Bud.

则子查询非空, 因为有 Bud Lite 元组, 因此就删除 Bud 元组.


现在, 假设变量 t 首先碰到的是 Bud Lite, 我们删除的是哪个元组呢?

答案: 我们删除的是 Bud Lite.

原因是: 删除操作是分两个阶段进行的:

  1. 对所有满足 WHERE 中条件的元组进行标记.
  2. 删除标记的元组.

更新(Updates)

更新(Updates)

要更新关系中某个元组的某些属性值, 采用下面的语句:

UPDATE <relation>
SET <list of attribute assignments>
WHERE <condition on tuples>;

例子: Update

将酒客 Fred 的电话号码更改为 555-1212:

UPDATE Drinkers
SET phone='555-1212'
WHERE name='Fred';

例子: 更改一些元组

假设 Sells 中啤酒的最高价格大于 4 美元. 现将超过 4 美元的啤酒统一定价为 4 美元:

UPDATE Sells
SET price=4.00
WHERE price>4.00;

实验

mysql> UPDATE Drinkers
    -> SET phone='555-1212'
    -> WHERE name='atzjg';
mysql> select * from Drinkers where name='atzjg';
+-------+---------------------+----------+
| name  | addr                | phone    |
+-------+---------------------+----------+
| atzjg | atzjg.net,atzjg.com | 555-1212 |
+-------+---------------------+----------+
1 row in set (0.01 sec)

在做上面最后一个实验时, 不妨将 Sells 表的数据备份一下. 在终端中执行下面的命令

mysqldump -uroot -h localhost -p test_bar Sells > Sells.sql

然后不妨更新一下数据, 先查看一下原始的价格.

mysql> select * from Sells where price > 40;
+--------------------------+-----------+-------+
| bar                      | beer      | price |
+--------------------------+-----------+-------+
| 扬州老啤酒厂酒吧         | 米勒      |    50 |
| 扬州老啤酒厂酒吧         | 贝克      |    42 |
| 木板房啤酒吧             | 嘉士伯    |    43 |
+--------------------------+-----------+-------+
3 rows in set (0.00 sec)
UPDATE Sells
SET price=51.00
WHERE price>40.00;

请使用 UPDATE 语句将数据改回去.

习题 6.5.4, P.175

习题

Product(maker, model, type)
PC(model, speed, ram, hd, price)
Laptop(model, speed, ram, hd, screen, price)
Printer(model, color, type, price)

通过两条 INSERT 语句在数据库中添加如下信息: 厂商 C 生产的型号为 1100 的 PC, 其速度为 3.2, RAM 容量大小为 1024 MB, 硬盘容量大小为 180 GB, 售价 $2499.

INSERT INTO Product VALUES ('C', '1100', 'PC');
INSERT INTO PC VALUES ('1100', 3.2, 1024, 160, 2499);

删除所有硬盘容量低于 100GB 的 PC.

DELETE FROM PC WHERE hd < 100;

删除所有不生产打印机的厂商所生成的笔记本电脑.

分析: 我们要删除的是笔记本电脑, 这是 Laptop 表中的信息, 但是相关的在 Product 中的信息也应该被删除. (可以使用外键, 级联删除. 这里我们暂时不用. 因此先删除 Laptop 中的相关元组, 然后再删除 Product 中相应的记录.)

生产打印机的厂商是

SELECT maker FROM Product WHERE type = 'printer';

使用 NOT IN 就可以判断哪些厂商是不生产打印机的. 然后对应地去看该厂商所生产的 Laptop 的型号, 对应这些型号, 在 Laptop 表中将它们删除.

test

生产打印机的厂商为

SELECT maker FROM product WHERE type="printer";

将它作为子查询, 那么不生产打印机的厂商可以使用 NOT IN 来判断

WHERE maker NOT IN (SELECT maker FROM product WHERE type="printer")

现在要从 laptop 表中删除那些来自于上述厂商的笔记本电脑

SELECT * FROM Laptop 
WHERE model IN
( SELECT model FROM Product
  WHERE type='laptop' AND maker NOT IN 
  (SELECT maker FROM Product WHERE type='printer')
);
SELECT * FROM Laptop NATURAL JOIN Product
WHERE maker NOT IN 
  (SELECT maker FROM Product WHERE type='printer')
;
DELETE FROM Laptop 
WHERE model IN
( SELECT model FROM Product
  WHERE type='laptop' AND maker NOT IN 
  (SELECT maker FROM Product WHERE type='printer')
);
注意下面的语句无法运行
DELETE FROM Laptop NATURAL JOIN Product
WHERE maker NOT IN 
  (SELECT maker FROM Product WHERE type='printer')
;

但是 Product 表中还有相应的数据, 我们也要将它们删除.

如果采用下面的语句

DELETE FROM Product 
WHERE model IN
( SELECT model FROM Product
  WHERE type='laptop' AND maker NOT IN 
  (SELECT maker FROM Product WHERE type='printer')
);

将会带来错误. You can't specify target table 'Product' for update in FROM clause.

DELETE FROM Product
WHERE type='laptop' AND model NOT IN 
  (SELECT model FROM Laptop);
首先 insert into laptop (model) ( SELECT model FROM Product WHERE type='laptop' AND maker NOT IN (SELECT maker FROM Product WHERE type='printer') ) 最后恢复数据
mysql -uroot -p test_products < D:\test_products.sql
UPDATE laptop NATURAL JOIN Product
SET screen=1, price=price-100
WHERE maker='B';

如果将 SET screen=1, price=price-100 写成了 SET screen=1 AND price=price-100

End






Thanks very much!

This slide is based on Jeffrey D. Ullman's work, which can be download from his website.