壬戌之秋,七月既望。眼看就要七月了,回首六月,好像离预期的目标还是太远。大规模仿真实验还在跑跑跑,参数还在调调调,糟心之余,唯一欣慰的是,总共完成了十篇博客,涵盖MySQL和机器学习。本次主要是总结MySQL中的联结和组合查询。
一、联结表
在阐述联结这个词之前我们先了解一些概念:
关系表
举个例子,这里有一个包含产品目录的数据库,
prod_id—产品编号
vend_id—供应商编号
prod_name—产品名称
prod_price—产品价格
prod_desc—产品描述
如果同一供应商生产多种物品,那么如何储存供应商消息?
一般的,我们会重新创建一个表格,来储存供应商的信息。因为可能会有多个产品对应同一供应商,如果放在同一表格的话,会造成数据重复。如果放在不同的表格,一旦供应商信息发生改变,只需要修改一次即可。
因此,我们为供应商单独建立了一个表格,
在关系型数据库中,各个表通过某一字段关联。Products表只储存产品信息,包括了供应商ID,vend_id表的主键又叫products的外键,它将products表和vendors关联起来。
外键:外键为某一个表的一个字段,同时又为另一个表的主键。
为什么要使用联结?
前面我们讲了,关系型数据库中多个表通过某一字段关联,这样更方便处理,更有效率。但问题是如果数据储存在多个表中,怎么样用单条SELECT语句把我们想要的数据检索出来呢?使用联结可以解决这个问题。联结是一种机制,用来在一条SELECT语句中关联表。
1.创建联结
创建一个联结,返回供应商名称以及提供的产品,
SELECT vend_name,prod_name,prod_price
FROM vendors,products
WHERE vendors.vend_id = products.vend_id;
结果:
在联结两个表时,第一个表的每一行与第二个表的每一行配对。WHERE子句作为过滤条件,它只包含那些匹配给定条件的行。如果没有WHERE子句,第一个表格的每一行会与第二个表格的每一行配对。
2.内部联结
我们之前在WHERE子句中所使用的联结用等号连接,称为等值联结,它基于两个表格的相等测试。这种联结也叫内部联结。
SELECT vend_name,prod_name,prod_price
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id;
结果:
3.联结多个表
一条SELECT语句可以联结多个表,数目没有限制,创建表的规则相同。
SELECT prod_name,vend_name,prod_name,quantity
FROM products a,vendors b,orderitems c
WHERE a.vend_id = b.vend_id AND a.prod_id = c.prod_id AND order_num = 20005;
结果:
在第十四章中,我们要列出订购物品TNT2的所有客户,之前使用的是子查询,由内到外依次展开,
SELECT cust_name,cust_contact
FROM customers
WHERE cust_id IN (SELECT cust_id
FROM orders
WHERE order_num IN (SELECT order_num
FROM orderitems
WHERE prod_id = 'TNT2'));
这里使用联结,其中WHERE子句中包含三个条件,前两个关联联结中的表,后一个是过滤产品ID为TNT2的数据。
SELECT cust_name,cust_contact
FROM orderitems a,orders b,customers c
WHERE a.order_num = b.order_num AND b.cust_id = c.cust_id AND a.prod_id ='TNT2';
结果:
二、创建高级联结
除了内联结以外,还有其他的联结类型。
1.使用表别名
为了缩短SQL语句,允许在单条SELECT语句中多次使用相同的表,我们使用别名。别名除了用于列名和计算字段之外,还可以用于表。其实前面我们已经使用过。
SELECT cust_name,cust_contact
FROM orderitems a,orders b,customers c
WHERE a.order_num = b.order_num AND b.cust_id = c.cust_id AND a.prod_id ='TNT2';
我们给三个表格分别取名a,b,c,在where子句里,我们就可以使用这些别名,而不用写全称。
2.使用不同类型的联结
前面我们在select语句中使用过内部联结(等值联结),这里再介绍三种联结方式
1)自联结
问题背景:假如你发现某物品(其ID为DTNTR)存在问题,因此想知道生产该物品的供应商生产的其他物品是否也有问题。
第一种方式:使用子查询
SELECT prod_id,prod_name
FROM products
WHERE vend_id IN(SELECT vend_id
FROM products
WHERE prod_id = 'DTNTR');
结果:
第二种方式:使用自联结
SELECT b.prod_id ,b.prod_name
FROM products a,products b
WHERE a.vend_id = b.vend_id AND a.prod_id = 'DTNTR';
结果相同,不再展示。使用的两个表是同一个表,实际上是同一个表使用了两次。
2)自然联结
自然联结其实和内部联结一样,只是不会出现相同的重复列。
SELECT c.*,o.order_num,o.order_date,oi.prod_id,oi.quantity,Oi.item_price
FROM customers AS c,orders AS o,orderitems AS oi
WHERE c.cust_id = o.cust_id AND oi.order_num = o.order_num AND prod_id = 'FB';
结果:
字体太小,但我们可以发现,没有重复的列出现。
而如果你直接使用内部联结,
SELECT *
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id;
结果:
可以发现,内部联结把将两个表中存在联结关系的字段符合联结关系的那些记录形成记录集的联结。这里就是把 id 相等的部分罗列出来。
3)外部联结
外部联结包含了在联结表中没有关联行的行,比如
- 对每个客户下了多少订单进行计数,包括哪些没有下订单的客户;
- 列出所有产品以及订购数量,包括没有被订购的产品;
外部联结包括左联结(left outer join on)和右联结(right outer join on)。
我们把内部联结和外部联结对比一下这里是查询顾客对应的订单:
内部联结:
SELECT a.cust_id,order_num
FROM customers a INNER JOIN orders b
ON a.cust_id = b.cust_id;
结果:
外部联结:
SELECT a.cust_id,order_num
FROM customers a LEFT OUTER JOIN orders b
ON a.cust_id = b.cust_id;
结果:
从这两个结果,我们可以发现,内部联结表把两个表中符合id相等的行取出来,而外部联结中,左边的行一定会列出,右边的表中如果没有匹配的行,则列值为Null。特别需要注意的是如果右表有多行和左表匹配,那么左表相同的行会出现多次。
3.使用带聚集函数的联结
之前的聚集函数只是在一个表中进行汇集数据。如果在多个表中汇集数据,需要使用联结。
比如,要检索所有客户以及每个客户下的订单数,
SELECT cust_name,c.cust_id,COUNT(*) AS order_num
FROM orders o INNER JOIN customers c
ON o.cust_id = c.cust_id
GROUP BY o.cust_id;
结果:
如果要包含那些没有下订单的顾客,
SELECT cust_name,c.cust_id,COUNT(order_num) AS order_num
FROM orders o RIGHT OUTER JOIN customers c
ON o.cust_id = c.cust_id
GROUP BY c.cust_id;
结果:
注意:
- 不要忘记提供联结条件,否则会得出笛卡尔积;
- 一个联结中可以包含多个表,每个联结可以包含不同的联结类型。在一起测试这些联结之前,应该分别测试每个联结,利于排除故障。
三、组合查询
之前我们所做的大部分查询都是包含从一个或多个表中返回查询结果的单条SELECT语句。MySQL允许多个查询(多条SELECT语句),并将结果作为单个查询结果返回。这些组合查询也被称为并-union或复合查询。
适用情况:
- 在单个查询中从不同的表返回类似结构的数据;
- 对单个表执行多个查询,按单个查询返回数据。
1.创建组合查询
用UNION操作符来组合多条SELECT查询,并将结果组合成单个结果集。
在使用UNION时,只需要在每条select语句之间加上关键字UNION即可。
这里,我们要将两个SELECT语句进行组合,
第一个是检索价格不高于5的物品,
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price <=5;
结果:
第二个是找出供应商1001和1002生产的所有物品,
SELECT vend_id,prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002);
结果:
我们将这两条语句进行组合,
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price <=5
UNION
SELECT vend_id,prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002);
结果:
当然这种比较简单的查询,我们可以只用 WHERE + OR 语句
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price <=5 OR vend_id IN (1001,1002);
2.使用UNION
有三条基本规则:
- UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔
- UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)
- 列的数据类型必须兼容:类型不必完全相同
去重
UNION从查询结果集中自动去除了重复的行,如果想返回所有匹配的行,可使用UNION ALL
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price <=5
UNION ALL
SELECT vend_id,prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002);
结果:
这里就包括了重复的行。一般地,UNION 几乎完成与多个 where 子句相同的工作。UNION ALL 为 UNION 的一种形式,它完成WHERE子句完成不了的工作。如果确实需要每个条件的匹配行全部出现,则必须使用 UNION ALL而不是 WHERE。
组合查询结果排序
单条select语句中的排序用 ORDER BY 排序。在用 UNION组合查询时,只能使用一条 ORDER BY ,它必须出现在最后一条 SELECT 语句之后。对于结果集,不允许用一种方式排序一部分,而又用另一种方式排序另一部分,不允许使用多条ORDER BY 子句。
对之前的组合结果使用ORDER BY 子句
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price <=5
UNION ALL
SELECT vend_id,prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002)
ORDER BY vend_id,prod_id;
结果:
四、经典面试题
问题描述如下:
为管理岗位业务培训信息,建立三个表格:
-
Stu(Sid,SN,SD,SA)表:学生表
Sid 表示学号
SN 表示学员姓名
SD 表示所属单位
SA 表示学员年龄 -
Cou(Cid,CN)表:课程表
Cid 课程编号
CN 课程名称 -
Sco(Sid,Cid,G)表:分数表
Sid 表示学号
Cid 课程编号
G 学习成绩
具体问题参考SQL脚本
################################################################################################################
-- 创建学生表
CREATE TABLE Stu(
Sid INT NOT NULL PRIMARY KEY,
SN VARCHAR(20),
SD VARCHAR(50),
SA INT
)ENGINE = INNODB;
-- 创建课程表
CREATE TABLE Cou(
Cid VARCHAR(10) NOT NULL PRIMARY KEY,
CN VARCHAR(20)
)ENGINE = INNODB;
-- 创建成绩表
CREATE TABLE Sco(
Sid INT NOT NULL,
Cid VARCHAR(10) NOT NULL,
G DECIMAL(5,2),
CONSTRAINT fk_Sid FOREIGN KEY (Sid) REFERENCES Stu(Sid),
CONSTRAINT fk_Cid FOREIGN KEY (Cid) REFERENCES Cou(Cid)
)ENGINE = INNODB;
#################################################################################################################
-- 填充数据
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019001,'托马斯李','运营',26);
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019002,'米高扬','管理',30);
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019003,'蝙蝠侠','安防',22);
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019004,'李嘉诚','投资',45);
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019005,'雷军','开发',34);
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019006,'周小川','管理',56);
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019007,'陆奇','运营',36);
INSERT INTO Stu(Sid,SN,SD,SA) VALUES(2019008,'普京','安防',67);
INSERT INTO Cou(Cid,CN) VALUES('C1','税收基础');
INSERT INTO Cou(Cid,CN) VALUES('C2','金融工程');
INSERT INTO Cou(Cid,CN) VALUES('C3','会计');
INSERT INTO Cou(Cid,CN) VALUES('C4','统计学习方法');
INSERT INTO Cou(Cid,CN) VALUES('C5','大数据');
INSERT INTO Cou(Cid,CN) VALUES('C6','机器学习算法');
INSERT INTO Sco(Sid,Cid,G) VALUES(2019001,'C2',80);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019002,'C2',78);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019003,'C1',89);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019003,'C5',60);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019004,'C4',90);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019005,'C1',87);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019005,'C2',75);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019005,'C3',80);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019005,'C4',90);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019005,'C5',86);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019005,'C6',88);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019006,'C1',99);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019006,'C2',61);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019007,'C1',62);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019007,'C2',78);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019007,'C3',77);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019007,'C4',69);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019007,'C5',98);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019007,'C6',88);
INSERT INTO Sco(Sid,Cid,G) VALUES(2019008,'C1',78);
#################################################################################################################
-- 一、使用标准SQL嵌套语言查询选修课程名称为“税收基础”的学员学号与姓名
SELECT Sid,SN
FROM Stu
WHERE Sid IN (SELECT Sid
FROM Sco
WHERE Cid IN (SELECT Cid
FROM Cou
WHERE CN = '税收基础'));
-- 二、使用标准SQL嵌套语言查询选修课程编号为“C2”的学员姓名和所属单位
SELECT SN,SD
FROM Stu
WHERE Sid IN (SELECT Sid
FROM Sco
WHERE Cid = 'C2');
-- 三、使用标准SQL嵌套语言查询选修课程编号为“C5”的学员姓名和所属单位
SELECT SN,SD
FROM Stu p1 INNER JOIN Sco p2
ON p1.Sid = p2.Sid AND p2.Cid = 'C5';
-- 四、使用标准SQ嵌套语言查询选修全部课程的学员姓名和所属单位
SELECT SN,SD
FROM Stu a ,Sco b
WHERE a.Sid = b.Sid
GROUP BY b.Sid
HAVING COUNT(Cid) = (SELECT COUNT(Cid)
FROM Cou);
-- 五、查询选修课程的学员人数
SELECT CN,COUNT(Sid)
FROM Cou s1 INNER JOIN Sco s2
ON s1.Cid = s2.Cid
GROUP BY s2.Cid;
-- 六、查询选修课程超过5门的学员学号和所属单位
SELECT s2.Sid,SD
FROM Sco s1 INNER JOIN Stu s2
ON s1.Sid = s2.Sid
GROUP BY s1.Sid
HAVING COUNT(Cid) > 5;
参考资料:
1.《MySQL必知必会》 Ben Forta;
2.sql面试题(学生表_课程表_成绩表_教师表):https://www.cnblogs.com/qixuejia/p/3637735.html