表的加减法
- 表的加法——UNION
首先创建一个名称为shop_list2的表便于代码演示,如下:
+---------+-----------+-----------+------------+-----------+---------------+
| shop_id | shop_name | shop_type | sell_price | buy_price | register_date |
+---------+-----------+-----------+------------+-----------+---------------+
| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2008-09-20 |
| 0002 | 打孔器 | 工具 | 500 | 320 | 2008-09-11 |
| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL |
| 0009 | 手套 | 衣服 | 800 | 500 | NULL |
| 0010 | 水壶 | 厨房用具 | 2000 | 1700 | 2009-09-20 |
+---------+-----------+-----------+------------+-----------+---------------+
接下来,将两张表相加:
SELECT shop_id,shop_name
FROM shop_list
UNION
SELECT shop_id,shop_name
FROM shop_list2;
+---------+-----------+
| shop_id | shop_name |
+---------+-----------+
| 0001 | T恤衫 |
| 0002 | 打孔器 |
| 0003 | 运动T恤 |
| 0004 | 菜刀 |
| 0005 | 砧板 |
| 0006 | 扳手 |
| 0009 | 手套 |
| 0010 | 水壶 |
+---------+-----------+
由表可以看出UNION集合就是并集,该集合运算符会除去重复的记录。
- 集合运算符的注意事项
1.作为运算对象的记录列数必须相同;
当两张表的列数不相同时,UNION是不能将两张表相加的。
2.作为运算对象的记录中列的类型必须一致;
不同类型的数据肯定不能存放在同一列的,如果非要相加的话,可以尝试类型转换。
3.可以使用任何SELECT 语句,但ORDER BY子句只能在最后使用一次;
示例:
SELECT shop_id,shop_name
FROM shop_list
WHERE shop_type = '厨房用具'
UNION
SELECT shop_id,shop_name
FROM shop_list2
WHERE shop_type='厨房用具'
ORDER BY shop_id;
+---------+-----------+
| shop_id | shop_name |
+---------+-----------+
| 0004 | 菜刀 |
| 0005 | 砧板 |
| 0010 | 水壶 |
+---------+-----------+
- 包含重复行的集合运算——ALL选项
UNION的结果没有包含重复行,那么只需要在UNION后面加上ALL关键字的结果就是可以显示所有行,包括重复行。
SELECT shop_id,shop_name
FROM shop_list
UNION ALL
SELECT shop_id,shop_name
FROM shop_list2;
+---------+-----------+
| shop_id | shop_name |
+---------+-----------+
| 0001 | T恤衫 |
| 0002 | 打孔器 |
| 0003 | 运动T恤 |
| 0004 | 菜刀 |
| 0005 | 砧板 |
| 0006 | 扳手 |
| 0001 | T恤衫 |
| 0002 | 打孔器 |
| 0003 | 运动T恤 |
| 0009 | 手套 |
| 0010 | 水壶 |
+---------+-----------+
- 选取表中公共部分——INTERSECT
INTERSECT即为获取两个表的交集。
SELECT shop_id,shop_name
FROM shop_list
INTERSECT
SELECT shop_id,shop_name
FROM shop_list2
ORDER BY shop_id;
MySQL暂时不支持INTERSECT用法。
- 表的减法——EXCEPT
EXCEPT即为表1减取与表2中重复的部分剩下的记录,用法与UNION相同。
SELECT shop_id,shop_name
FROM shop_list
EXCEPT
SELECT shop_id,shop_name
FROM shop_list2
ORDER BY shop_id;
Oracle不适用EXCEPT,其拥有特有的MINUS代替EXCEPT,MySQL暂时不支持EXCEPT这种用法。
联结(以列为单位对表进行联结)
前面学习的集合运算都是造成了行的变化,而联结的作用就是进行列的变化。
- 内联结——INNER JOIN
演示代码将会用到两张表:
+---------+-----------+-----------+------------+-----------+---------------+
| shop_id | shop_name | shop_type | sell_price | buy_price | register_date |
+---------+-----------+-----------+------------+-----------+---------------+
| 0001 | T恤衫 | 衣服 | 2000 | 500 | 2009-09-20 |
| 0002 | 打孔器 | 工具 | 600 | 100 | 2009-09-20 |
| 0003 | 运动T恤 | 衣服 | 3000 | 2800 | 2009-09-20 |
| 0004 | 菜刀 | 厨房用具 | 6000 | 2800 | 2009-09-20 |
| 0005 | 砧板 | 厨房用具 | 2000 | 1299 | 2009-09-20 |
| 0006 | 扳手 | 工具 | 3000 | 1500 | 2009-09-20 |
+---------+-----------+-----------+------------+-----------+---------------+
+---------+-----------+---------+--------+
| area_id | area_name | shop_id | number |
+---------+-----------+---------+--------+
| 000A | 北京 | 0001 | 30 |
| 000A | 北京 | 0002 | 50 |
| 000A | 北京 | 0003 | 15 |
| 000B | 上海 | 0002 | 30 |
| 000B | 上海 | 0003 | 120 |
| 000B | 上海 | 0004 | 20 |
| 000B | 上海 | 0006 | 10 |
| 000B | 上海 | 0007 | 40 |
| 000C | 重庆 | 0003 | 20 |
| 000C | 重庆 | 0004 | 50 |
| 000C | 重庆 | 0006 | 90 |
| 000C | 重庆 | 0007 | 70 |
| 000D | 杭州 | 0001 | 100 |
+---------+-----------+---------+--------+
如上表,两张表的列可以分为如下两类:
1.两张表中有包含相同的列→shop_id;
2.只存在与一张表内的列→除shop_id以外的列;
所谓联结,就是"以1中的的列作为桥梁,将2中的满足其他条件的列汇集到同一结果中"。
我们可以试着从shop_list中取出shop_name和sell_price与shop_area中的内容进行结合。
SELECT SA.area_id,SA.area_name,SA.shop_id,SL.shop_name,SL.sell_price
FROM shop_area AS SA JOIN shop_list AS SL
ON SA.shop_id=SL.shop_id;
+---------+-----------+---------+-----------+------------+
| area_id | area_name | shop_id | shop_name | sell_price |
+---------+-----------+---------+-----------+------------+
| 000A | 北京 | 0001 | T恤衫 | 2000 |
| 000A | 北京 | 0002 | 打孔器 | 600 |
| 000A | 北京 | 0003 | 运动T恤 | 3000 |
| 000B | 上海 | 0002 | 打孔器 | 600 |
| 000B | 上海 | 0003 | 运动T恤 | 3000 |
| 000B | 上海 | 0004 | 菜刀 | 6000 |
| 000B | 上海 | 0006 | 扳手 | 3000 |
| 000C | 重庆 | 0003 | 运动T恤 | 3000 |
| 000C | 重庆 | 0004 | 菜刀 | 6000 |
| 000C | 重庆 | 0006 | 扳手 | 3000 |
| 000D | 杭州 | 0001 | T恤衫 | 2000 |
+---------+-----------+---------+-----------+------------+
联结需要注意的点:
1.FROM子句:因为联结需要用到两张或多张表,使用关键字INNER JOIN就可以把两张表联结在一起,虽然使用别名不是必须的,但是过长的表名会影响可读性,所以尽量使用别名。
2.ON子句:ON后面记载的是联结条件,我们可以在ON后面指定联结所使用的列(联结键),ON子句在进行联结时是必不可少的,并且必须书写在FROM和WHERE之间。
3.SELECT子句:由于使用多张表,所以列的归属会非常混乱。所以为了避免混淆列的归属,可以使用<表的别名>.<列名>的方法书写。
内联结和WHERE子句结合使用
如果不想了解所有地区的情况,只想了解上海地区的情况,可以在联结语句中加入WHERE来限定条件。
SELECT SA.area_id,SA.area_name,SA.shop_id,SL.shop_name,SL.sell_price
FROM shop_area AS SA JOIN shop_list AS SL
ON SA.shop_id=SL.shop_id
WHERE SA.area_id='000B';
+---------+-----------+---------+-----------+------------+
| area_id | area_name | shop_id | shop_name | sell_price |
+---------+-----------+---------+-----------+------------+
| 000B | 上海 | 0002 | 打孔器 | 600 |
| 000B | 上海 | 0003 | 运动T恤 | 3000 |
| 000B | 上海 | 0004 | 菜刀 | 6000 |
| 000B | 上海 | 0006 | 扳手 | 3000 |
+---------+-----------+---------+-----------+------------+
像这样的联结运算结果,不仅可以使用WHERE子句,还能使用GROUP BY、HAVING、ORDER BY等。准确地说,可以完全把它看作一张新的表。
- 外联结——OUTER JOIN
外联结也是通过ON子句使用联结键将两张表进行联结,同时从两张表中选出相应的列,基本使用方法没有改变,但是结果却有所不同。
SELECT SA.area_id,SA.area_name,SA.shop_id,SL.shop_name,SL.sell_price
FROM shop_area AS SA RIGHT OUTER JOIN shop_list AS SL
ON SA.shop_id=SL.shop_id;
+---------+-----------+---------+-----------+------------+
| area_id | area_name | shop_id | shop_name | sell_price |
+---------+-----------+---------+-----------+------------+
| 000A | 北京 | 0001 | T恤衫 | 2000 |
| 000A | 北京 | 0002 | 打孔器 | 600 |
| 000A | 北京 | 0003 | 运动T恤 | 3000 |
| 000B | 上海 | 0002 | 打孔器 | 600 |
| 000B | 上海 | 0003 | 运动T恤 | 3000 |
| 000B | 上海 | 0004 | 菜刀 | 6000 |
| 000B | 上海 | 0006 | 扳手 | 3000 |
| 000C | 重庆 | 0003 | 运动T恤 | 3000 |
| 000C | 重庆 | 0004 | 菜刀 | 6000 |
| 000C | 重庆 | 0006 | 扳手 | 3000 |
| 000D | 杭州 | 0001 | T恤衫 | 2000 |
| NULL | NULL | NULL | 砧板 | 2000 |
+---------+-----------+---------+-----------+------------+
即使联结键在两张表中没有都存在,该行还是会进行联结,不存在的数据以NULL显示。
外联结要点:
1.选取出单张表中的全部的信息:只要数据存在于某一个表中,就能够读取出来。
2.外联结的主表:外联结中的主表很重要,哪张为主表,那么最总的结果中会包含这张主表内的所有数据。指定主表的关键字是LEFT和RIGTH,使用LEFT时FROM子句写在左侧的为主表,反之右侧为主表。
- 3张以上表的联结
原则上,表的联结对表的数量没有限制。
首先创建一张名称为storage的表作为第三张需要联结的表:
+------------+---------+----------------+
| storage_id | shop_id | storage_number |
+------------+---------+----------------+
| S001 | 0001 | 0 |
| S001 | 0002 | 120 |
| S001 | 0003 | 200 |
| S001 | 0004 | 3 |
| S001 | 0005 | 0 |
| S001 | 0006 | 99 |
| S001 | 0007 | 999 |
| S001 | 0008 | 200 |
| S002 | 0001 | 10 |
| S002 | 0002 | 25 |
| S002 | 0003 | 34 |
| S002 | 0004 | 19 |
| S002 | 0005 | 99 |
| S002 | 0006 | 0 |
| S002 | 0007 | 0 |
| S002 | 0008 | 18 |
+------------+---------+----------------+
对第三张表进行联结
SELECT SA.area_id,SA.area_name,SA.shop_id,SL.shop_name,SL.sell_price,ST.storage_number
FROM shop_area AS SA INNER JOIN shop_list AS SL
ON SA.shop_id=SL.shop_id
INNER JOIN storage AS ST
ON SA.shop_id=ST.shop_id
WHERE ST.storage_id='S001';
+---------+-----------+---------+-----------+------------+----------------+
| area_id | area_name | shop_id | shop_name | sell_price | storage_number |
+---------+-----------+---------+-----------+------------+----------------+
| 000A | 北京 | 0001 | T恤衫 | 2000 | 0 |
| 000A | 北京 | 0002 | 打孔器 | 600 | 120 |
| 000A | 北京 | 0003 | 运动T恤 | 3000 | 200 |
| 000B | 上海 | 0002 | 打孔器 | 600 | 120 |
| 000B | 上海 | 0003 | 运动T恤 | 3000 | 200 |
| 000B | 上海 | 0004 | 菜刀 | 6000 | 3 |
| 000B | 上海 | 0006 | 扳手 | 3000 | 99 |
| 000C | 重庆 | 0003 | 运动T恤 | 3000 | 200 |
| 000C | 重庆 | 0004 | 菜刀 | 6000 | 3 |
| 000C | 重庆 | 0006 | 扳手 | 3000 | 99 |
| 000D | 杭州 | 0001 | T恤衫 | 2000 | 0 |
+---------+-----------+---------+-----------+------------+----------------+
可以简单地理解成,将1,2两张表先联结,然后将重新生成的表再与表3进行联结。
- 交叉联结——CROSS JOIN
SELECT SA.area_id,SA.area_name,SA.shop_id,SL.shop_name
FROM shop_area AS SA CROSS JOIN shop_list AS SL;
+---------+-----------+---------+-----------+
| area_id | area_name | shop_id | shop_name |
+---------+-----------+---------+-----------+
| 000A | 北京 | 0001 | T恤衫 |
| 000A | 北京 | 0001 | 打孔器 |
| 000A | 北京 | 0001 | 运动T恤 |
| 000A | 北京 | 0001 | 菜刀 |
| 000A | 北京 | 0001 | 砧板 |
| 000A | 北京 | 0001 | 扳手 |
| 000A | 北京 | 0002 | T恤衫 |
| 000A | 北京 | 0002 | 打孔器 |
| 000A | 北京 | 0002 | 运动T恤 |
| 000A | 北京 | 0002 | 菜刀 |
| 000A | 北京 | 0002 | 砧板 |
| 000A | 北京 | 0002 | 扳手 |
| 000A | 北京 | 0003 | T恤衫 |
| 000A | 北京 | 0003 | 打孔器 |
| 000A | 北京 | 0003 | 运动T恤 |
| 000A | 北京 | 0003 | 菜刀 |
| 000A | 北京 | 0003 | 砧板 |
| 000A | 北京 | 0003 | 扳手 |
| 000B | 上海 | 0002 | T恤衫 |
| 000B | 上海 | 0002 | 打孔器 |
| 000B | 上海 | 0002 | 运动T恤 |
| 000B | 上海 | 0002 | 菜刀 |
| 000B | 上海 | 0002 | 砧板 |
| 000B | 上海 | 0002 | 扳手 |
| 000B | 上海 | 0003 | T恤衫 |
| 000B | 上海 | 0003 | 打孔器 |
| 000B | 上海 | 0003 | 运动T恤 |
| 000B | 上海 | 0003 | 菜刀 |
| 000B | 上海 | 0003 | 砧板 |
| 000B | 上海 | 0003 | 扳手 |
| 000B | 上海 | 0004 | T恤衫 |
| 000B | 上海 | 0004 | 打孔器 |
| 000B | 上海 | 0004 | 运动T恤 |
| 000B | 上海 | 0004 | 菜刀 |
| 000B | 上海 | 0004 | 砧板 |
| 000B | 上海 | 0004 | 扳手 |
| 000B | 上海 | 0006 | T恤衫 |
| 000B | 上海 | 0006 | 打孔器 |
| 000B | 上海 | 0006 | 运动T恤 |
| 000B | 上海 | 0006 | 菜刀 |
| 000B | 上海 | 0006 | 砧板 |
| 000B | 上海 | 0006 | 扳手 |
| 000B | 上海 | 0007 | T恤衫 |
| 000B | 上海 | 0007 | 打孔器 |
| 000B | 上海 | 0007 | 运动T恤 |
| 000B | 上海 | 0007 | 菜刀 |
| 000B | 上海 | 0007 | 砧板 |
| 000B | 上海 | 0007 | 扳手 |
| 000C | 重庆 | 0003 | T恤衫 |
| 000C | 重庆 | 0003 | 打孔器 |
| 000C | 重庆 | 0003 | 运动T恤 |
| 000C | 重庆 | 0003 | 菜刀 |
| 000C | 重庆 | 0003 | 砧板 |
| 000C | 重庆 | 0003 | 扳手 |
| 000C | 重庆 | 0004 | T恤衫 |
| 000C | 重庆 | 0004 | 打孔器 |
| 000C | 重庆 | 0004 | 运动T恤 |
| 000C | 重庆 | 0004 | 菜刀 |
| 000C | 重庆 | 0004 | 砧板 |
| 000C | 重庆 | 0004 | 扳手 |
| 000C | 重庆 | 0006 | T恤衫 |
| 000C | 重庆 | 0006 | 打孔器 |
| 000C | 重庆 | 0006 | 运动T恤 |
| 000C | 重庆 | 0006 | 菜刀 |
| 000C | 重庆 | 0006 | 砧板 |
| 000C | 重庆 | 0006 | 扳手 |
| 000C | 重庆 | 0007 | T恤衫 |
| 000C | 重庆 | 0007 | 打孔器 |
| 000C | 重庆 | 0007 | 运动T恤 |
| 000C | 重庆 | 0007 | 菜刀 |
| 000C | 重庆 | 0007 | 砧板 |
| 000C | 重庆 | 0007 | 扳手 |
| 000D | 杭州 | 0001 | T恤衫 |
| 000D | 杭州 | 0001 | 打孔器 |
| 000D | 杭州 | 0001 | 运动T恤 |
| 000D | 杭州 | 0001 | 菜刀 |
| 000D | 杭州 | 0001 | 砧板 |
| 000D | 杭州 | 0001 | 扳手 |
+---------+-----------+---------+-----------+
交叉联结时,无法使用ON子句,这是因为交叉联结是对两张表内全部记录进行交叉组合,因此结果中记录数通常是两张表的行数乘积。