多表查询
文章目录
- 多表查询
- 一、链表查询
- 1.1交叉连接
- 1.2 内连接
- 1.3 左连接
- 1.4 右连接
- 1.5 全连接
- 1.6 例子
- 二、子查询
- 2.1 in与not in
- 2.2 any/some
- 2.3 all
- 2.4 比较运算符
- 2.5 exists
- 三、例子
查询中使用的表如下所示
+----+--------+
| id | name |
+----+--------+
| 1 | IT |
| 2 | 销售 |
| 3 | 运营 |
| 4 | 安保 |
+----+--------+
+----+--------+------+--------+
| id | name | age | dep_id |
+----+--------+------+--------+
| 1 | 张三 | 20 | 1 |
| 2 | 李四 | 25 | 3 |
| 3 | 王五 | 30 | 2 |
| 4 | 赵六 | 31 | 2 |
| 5 | 阿七 | 18 | NULL |
+----+--------+------+--------+
一、链表查询
链表查询的本质是将多张表拼接在一块形成一张新表,再对新表进行查询。
SELECT 字段 FROM 表1 INNER|LEFT|RIGHT JOIN 表2 ON 表1.字段 = 表2.字段;
1.1交叉连接
#交叉连接不能加任何的匹配条件,生成的结果是两张表的笛卡尔积
select * from employee,department;
+----+--------+------+--------+----+--------+
| id | name | age | dep_id | id | name |
+----+--------+------+--------+----+--------+
| 1 | 张三 | 20 | 1 | 4 | 安保 |
| 1 | 张三 | 20 | 1 | 3 | 运营 |
| 1 | 张三 | 20 | 1 | 2 | 销售 |
| 1 | 张三 | 20 | 1 | 1 | IT |
| 2 | 李四 | 25 | 3 | 4 | 安保 |
| 2 | 李四 | 25 | 3 | 3 | 运营 |
| 2 | 李四 | 25 | 3 | 2 | 销售 |
| 2 | 李四 | 25 | 3 | 1 | IT |
| 3 | 王五 | 30 | 2 | 4 | 安保 |
| 3 | 王五 | 30 | 2 | 3 | 运营 |
| 3 | 王五 | 30 | 2 | 2 | 销售 |
| 3 | 王五 | 30 | 2 | 1 | IT |
| 4 | 赵六 | 31 | 2 | 4 | 安保 |
| 4 | 赵六 | 31 | 2 | 3 | 运营 |
| 4 | 赵六 | 31 | 2 | 2 | 销售 |
| 4 | 赵六 | 31 | 2 | 1 | IT |
| 5 | 阿七 | 18 | NULL | 4 | 安保 |
| 5 | 阿七 | 18 | NULL | 3 | 运营 |
| 5 | 阿七 | 18 | NULL | 2 | 销售 |
| 5 | 阿七 | 18 | NULL | 1 | IT |
+----+--------+------+--------+----+--------+
1.2 内连接
#内连接是根据匹配条件将两张表的公共部分连接起来
select * from employee inner join department on employee.dep_id=department.id;
+----+--------+------+--------+----+--------+
| id | name | age | dep_id | id | name |
+----+--------+------+--------+----+--------+
| 1 | 张三 | 20 | 1 | 1 | IT |
| 2 | 李四 | 25 | 3 | 3 | 运营 |
| 3 | 王五 | 30 | 2 | 2 | 销售 |
| 4 | 赵六 | 31 | 2 | 2 | 销售 |
+----+--------+------+--------+----+--------+
1.3 左连接
#左连接是以左表为基准将右表符合匹配条件的信息拼接到左表,左表中匹配不上的记录会被保留
select * from employee left join department on employee.dep_id=department.id;
+----+--------+------+--------+------+--------+
| id | name | age | dep_id | id | name |
+----+--------+------+--------+------+--------+
| 1 | 张三 | 20 | 1 | 1 | IT |
| 2 | 李四 | 25 | 3 | 3 | 运营 |
| 3 | 王五 | 30 | 2 | 2 | 销售 |
| 4 | 赵六 | 31 | 2 | 2 | 销售 |
| 5 | 阿七 | 18 | NULL | NULL | NULL |
+----+--------+------+--------+------+--------+
1.4 右连接
#右连接是以右表为基准将左表符合匹配条件的信息拼接到右表,右表中匹配不上的记录会被保留
select * from employee right join department on employee.dep_id=department.id;
+------+--------+------+--------+----+--------+
| id | name | age | dep_id | id | name |
+------+--------+------+--------+----+--------+
| 1 | 张三 | 20 | 1 | 1 | IT |
| 4 | 赵六 | 31 | 2 | 2 | 销售 |
| 3 | 王五 | 30 | 2 | 2 | 销售 |
| 2 | 李四 | 25 | 3 | 3 | 运营 |
| NULL | NULL | NULL | NULL | 4 | 安保 |
+------+--------+------+--------+----+--------+
1.5 全连接
#全连接是将两张表根据匹配条件拼接记录,同时保留两张表中匹配不到的记录。
#由于mysql不支持full join操作,所以使用union来模拟全连接操作
#union的作用是将两张表合并,这个关键字会自动完成去重、排序等操作。
select * from employee left join department on employee.dep_id=department.id
union
select * from employee right join department on employee.dep_id=department.id;
+------+--------+------+--------+------+--------+
| id | name | age | dep_id | id | name |
+------+--------+------+--------+------+--------+
| 1 | 张三 | 20 | 1 | 1 | IT |
| 2 | 李四 | 25 | 3 | 3 | 运营 |
| 3 | 王五 | 30 | 2 | 2 | 销售 |
| 4 | 赵六 | 31 | 2 | 2 | 销售 |
| 5 | 阿七 | 18 | NULL | NULL | NULL |
| NULL | NULL | NULL | NULL | 4 | 安保 |
+------+--------+------+--------+------+--------+
1.6 例子
#现在需要针对上面的表格查询出销售部门的员工信息并按年龄升序排序
#在链表操作中,如果字段只有某一张表含有,可以直接写字段不用写为表.字段的形式
#如下方order by后的age只有employee有,可以直接写age,不用写为employee.age
select * from employee inner join department on employee.dep_id=department.id
where department.name='销售' order by age;
+----+--------+------+--------+----+--------+
| id | name | age | dep_id | id | name |
+----+--------+------+--------+----+--------+
| 3 | 王五 | 30 | 2 | 2 | 销售 |
| 4 | 赵六 | 31 | 2 | 2 | 销售 |
+----+--------+------+--------+----+--------+
#查询IT部门的员工姓名、年龄信息
select employee.id,employee.name,age from employee inner join department
on employee.dep_id=department.id where department.name='IT';
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 张三 | 20 |
+----+--------+------+
二、子查询
子查询是指先查询一张表的信息,将查询到的信息作为另一张表的查询条件继续查询,以此重复直到查询出结果。
2.1 in与not in
#查询销售部门的员工信息
#下面的查询语句中先查询department表销售部门的id,再查询dep_id与id一致的员工信息
#需要注意的是如果select后只有一个字段,查询结果返回的是一个集合可以使用in,如果select后是多个字段查询结果是一张表无法使用in
select * from employee where dep_id in (select id from department where name='销售');
+----+--------+------+--------+
| id | name | age | dep_id |
+----+--------+------+--------+
| 3 | 王五 | 30 | 2 |
| 4 | 赵六 | 31 | 2 |
+----+--------+------+--------+
#需要注意not in无法处理null
#例如寻找没有员工的部门,由于括号中的查询结果返回集合含有null,not in无法处理以此结果为空
select * from department where id not in (select dep_id from employee);
Empty set (0.00 sec)
#对上述的问题可以这样改进
select * from department where id not in (select dep_id from employee where dep_id is not null);
+----+--------+
| id | name |
+----+--------+
| 4 | 安保 |
+----+--------+
2.2 any/some
any表示某一个的意思,它与in的区别是any后面必须是查询的结果,不能直接是集合。
mysql中some的用法和any完全一致。
例如select * from t1 where id in (1,2);可以,但是select * from t1 where id=any (1,2);则不行。
#查询每个部门年龄最大的员工
#age=any和age in其实是等价的
#而>any的意思是比集合中某一个大,翻译一下就是比集合中最小的那个大就行,同样的<any的意思是比集合中某一个小,翻译一下就是比集合中最大的那个小就行
select * from employee where age=
any(select max(age) from employee where dep_id is not null group by dep_id);
+----+--------+------+--------+
| id | name | age | dep_id |
+----+--------+------+--------+
| 1 | 张三 | 20 | 1 |
| 2 | 李四 | 25 | 3 |
| 4 | 赵六 | 31 | 2 |
+----+--------+------+--------+
2.3 all
all表示所有的意思,all和any一样后面不能直接加集合,只能比较查询结果
#查询年龄比所有部门平均年龄高的员工信息
select * from employee where age>
all(select avg(age) from employee where dep_id is not null group by dep_id);
+----+--------+------+--------+
| id | name | age | dep_id |
+----+--------+------+--------+
| 4 | 赵六 | 31 | 2 |
+----+--------+------+--------+
#查询年龄比某一个部门平均年龄高的员工信息
select * from employee where age>
any(select avg(age) from employee where dep_id is not null group by dep_id);
+----+--------+------+--------+
| id | name | age | dep_id |
+----+--------+------+--------+
| 2 | 李四 | 25 | 3 |
| 3 | 王五 | 30 | 2 |
| 4 | 赵六 | 31 | 2 |
+----+--------+------+--------+
2.4 比较运算符
比较运算符有=、!=、>、>=、<、<=、<>
#查询年龄比所有员工平均年龄大的员工信息
select * from employee where age>(select avg(age) from employee);
+----+--------+------+--------+
| id | name | age | dep_id |
+----+--------+------+--------+
| 2 | 李四 | 25 | 3 |
| 3 | 王五 | 30 | 2 |
| 4 | 赵六 | 31 | 2 |
+----+--------+------+--------+
2.5 exists
exists关字键字表示存在。使用exists时,内层查询语句不返回查询的记录,而是返回一个真假值。当内存查询语句查询到记录时返回True,外层查询语句返回结果,反之则返回False,外层查询语句不返回结果。
exists的执行过程:
- 依次执外部查询
- 然后为外部查询返回的每一行分别执行一次子查询
- 子查询如果返回记录,则exists条件成立,外部查询语句返回结果
下面拿exists和in的执行进行比较,in的子查询会先产生结果集,然后外部查询再去结果集里去找符合要求的字段返回结果。由此可以看出exists的查询效率是远不如in的。
#由于department中没有id=5的记录,使用exists返回False,以此外层的查询语句不执行
select * from employee where exists(select * from department where id=5);
Empty set (0.00 sec)
#查询有部门的员工的员工信息
select * from employee where
exists(select * from department where employee.dep_id=department.id);
+----+--------+------+--------+
| id | name | age | dep_id |
+----+--------+------+--------+
| 1 | 张三 | 20 | 1 |
| 2 | 李四 | 25 | 3 |
| 3 | 王五 | 30 | 2 |
| 4 | 赵六 | 31 | 2 |
+----+--------+------+--------+
三、例子
使用到的表生成代码如下:
/*
数据导入:
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50624
Source Host : localhost
Source Database : sqlexam
Target Server Type : MySQL
Target Server Version : 50624
File Encoding : utf-8
Date: 10/21/2016 06:46:46 AM
*/
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for `class`
-- ----------------------------
DROP TABLE IF EXISTS `class`;
CREATE TABLE `class` (
`cid` int(11) NOT NULL AUTO_INCREMENT,
`caption` varchar(32) NOT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of `class`
-- ----------------------------
BEGIN;
INSERT INTO `class` VALUES ('1', '三年二班'), ('2', '三年三班'), ('3', '一年二班'), ('4', '二年九班');
COMMIT;
-- ----------------------------
-- Table structure for `course`
-- ----------------------------
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
`cid` int(11) NOT NULL AUTO_INCREMENT,
`cname` varchar(32) NOT NULL,
`teacher_id` int(11) NOT NULL,
PRIMARY KEY (`cid`),
KEY `fk_course_teacher` (`teacher_id`),
CONSTRAINT `fk_course_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`tid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of `course`
-- ----------------------------
BEGIN;
INSERT INTO `course` VALUES ('1', '生物', '1'), ('2', '物理', '2'), ('3', '体育', '3'), ('4', '美术', '2');
COMMIT;
-- ----------------------------
-- Table structure for `score`
-- ----------------------------
DROP TABLE IF EXISTS `score`;
CREATE TABLE `score` (
`sid` int(11) NOT NULL AUTO_INCREMENT,
`student_id` int(11) NOT NULL,
`course_id` int(11) NOT NULL,
`num` int(11) NOT NULL,
PRIMARY KEY (`sid`),
KEY `fk_score_student` (`student_id`),
KEY `fk_score_course` (`course_id`),
CONSTRAINT `fk_score_course` FOREIGN KEY (`course_id`) REFERENCES `course` (`cid`),
CONSTRAINT `fk_score_student` FOREIGN KEY (`student_id`) REFERENCES `student` (`sid`)
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of `score`
-- ----------------------------
BEGIN;
INSERT INTO `score` VALUES ('1', '1', '1', '10'), ('2', '1', '2', '9'), ('5', '1', '4', '66'), ('6', '2', '1', '8'), ('8', '2', '3', '68'), ('9', '2', '4', '99'), ('10', '3', '1', '77'), ('11', '3', '2', '66'), ('12', '3', '3', '87'), ('13', '3', '4', '99'), ('14', '4', '1', '79'), ('15', '4', '2', '11'), ('16', '4', '3', '67'), ('17', '4', '4', '100'), ('18', '5', '1', '79'), ('19', '5', '2', '11'), ('20', '5', '3', '67'), ('21', '5', '4', '100'), ('22', '6', '1', '9'), ('23', '6', '2', '100'), ('24', '6', '3', '67'), ('25', '6', '4', '100'), ('26', '7', '1', '9'), ('27', '7', '2', '100'), ('28', '7', '3', '67'), ('29', '7', '4', '88'), ('30', '8', '1', '9'), ('31', '8', '2', '100'), ('32', '8', '3', '67'), ('33', '8', '4', '88'), ('34', '9', '1', '91'), ('35', '9', '2', '88'), ('36', '9', '3', '67'), ('37', '9', '4', '22'), ('38', '10', '1', '90'), ('39', '10', '2', '77'), ('40', '10', '3', '43'), ('41', '10', '4', '87'), ('42', '11', '1', '90'), ('43', '11', '2', '77'), ('44', '11', '3', '43'), ('45', '11', '4', '87'), ('46', '12', '1', '90'), ('47', '12', '2', '77'), ('48', '12', '3', '43'), ('49', '12', '4', '87'), ('52', '13', '3', '87');
COMMIT;
-- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`sid` int(11) NOT NULL AUTO_INCREMENT,
`gender` char(1) NOT NULL,
`class_id` int(11) NOT NULL,
`sname` varchar(32) NOT NULL,
PRIMARY KEY (`sid`),
KEY `fk_class` (`class_id`),
CONSTRAINT `fk_class` FOREIGN KEY (`class_id`) REFERENCES `class` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of `student`
-- ----------------------------
BEGIN;
INSERT INTO `student` VALUES ('1', '男', '1', '理解'), ('2', '女', '1', '钢蛋'), ('3', '男', '1', '张三'), ('4', '男', '1', '张一'), ('5', '女', '1', '张二'), ('6', '男', '1', '张四'), ('7', '女', '2', '铁锤'), ('8', '男', '2', '李三'), ('9', '男', '2', '李一'), ('10', '女', '2', '李二'), ('11', '男', '2', '李四'), ('12', '女', '3', '如花'), ('13', '男', '3', '刘三'), ('14', '男', '3', '刘一'), ('15', '女', '3', '刘二'), ('16', '男', '3', '刘四');
COMMIT;
-- ----------------------------
-- Table structure for `teacher`
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
`tid` int(11) NOT NULL AUTO_INCREMENT,
`tname` varchar(32) NOT NULL,
PRIMARY KEY (`tid`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of `teacher`
-- ----------------------------
BEGIN;
INSERT INTO `teacher` VALUES ('1', '张磊老师'), ('2', '李平老师'), ('3', '刘海燕老师'), ('4', '朱云海老师'), ('5', '李杰老师');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
例子中表的结构如下所示:
现在需要针对表中的数据进行如下的操作:
#查询物理课程比生物课程高的学生的学号
select b.student_id from
(select student_id,num as biology from score where course_id in
(select cid from course where cname='生物')) as b
inner join
(select student_id,num as physcis from score where course_id in
(select cid from course where cname='物理')) as p
on b.student_id=p.student_id
where b.biology<p.physcis;
+------------+
| student_id |
+------------+
| 6 |
| 7 |
| 8 |
+------------+
#查询挂科超过两门(包括两门)的学生姓名和班级
select sname,caption from class inner join student on cid=sid where sid in
(select student_id from score where num<60 group by student_id having count(num)>=2);
+--------+--------------+
| sname | caption |
+--------+--------------+
| 理解 | 三年二班 |
+--------+--------------+
#查询所有选修了学号为1的同学选修过的一门或者多门课程的同学学号和姓名;
select distinct sname,student.sid from student inner join score on student_id=student.sid
where course_id in(select course_id from score where student_id=1) and student_id!=1;
+--------+-----+
| sname | sid |
+--------+-----+
| 钢蛋 | 2 |
| 张三 | 3 |
| 张一 | 4 |
| 张二 | 5 |
| 张四 | 6 |
| 铁锤 | 7 |
| 李三 | 8 |
| 李一 | 9 |
| 李二 | 10 |
| 李四 | 11 |
| 如花 | 12 |
+--------+-----+
#查询在所有选修了李平老师课程的学生中,这些课程(李平老师的课程,不是所有课程)平均成绩最高的学生姓名
select sname,avg(num) from student inner join score on student.sid=student_id where course_id
in((select cid from course inner join teacher on tid=teacher_id where tname='李平老师'))
group by sname order by avg(num) desc limit 1;
+--------+----------+
| sname | avg(num) |
+--------+----------+
| 张四 | 100.0000 |
+--------+----------+
#查询不同课程但成绩相同的学号,课程号,成绩
select s1.student_id,s1.course_id,s1.num,s2.student_id,s2.course_id,s2.num
from score as s1,score as s2
where s1.num=s2.num and s1.course_id!=s2.course_id;
+------------+-----------+-----+------------+-----------+-----+
| student_id | course_id | num | student_id | course_id | num |
+------------+-----------+-----+------------+-----------+-----+
| 8 | 1 | 9 | 1 | 2 | 9 |
| 7 | 1 | 9 | 1 | 2 | 9 |
| 6 | 1 | 9 | 1 | 2 | 9 |
| 3 | 2 | 66 | 1 | 4 | 66 |
| 12 | 2 | 77 | 3 | 1 | 77 |
| 11 | 2 | 77 | 3 | 1 | 77 |
| 10 | 2 | 77 | 3 | 1 | 77 |
| 1 | 4 | 66 | 3 | 2 | 66 |
| 12 | 4 | 87 | 3 | 3 | 87 |
| 11 | 4 | 87 | 3 | 3 | 87 |
| 10 | 4 | 87 | 3 | 3 | 87 |
| 8 | 2 | 100 | 4 | 4 | 100 |
| 7 | 2 | 100 | 4 | 4 | 100 |
| 6 | 2 | 100 | 4 | 4 | 100 |
| 8 | 2 | 100 | 5 | 4 | 100 |
| 7 | 2 | 100 | 5 | 4 | 100 |
| 6 | 2 | 100 | 5 | 4 | 100 |
| 1 | 2 | 9 | 6 | 1 | 9 |
| 6 | 4 | 100 | 6 | 2 | 100 |
| 5 | 4 | 100 | 6 | 2 | 100 |
| 4 | 4 | 100 | 6 | 2 | 100 |
| 8 | 2 | 100 | 6 | 4 | 100 |
| 7 | 2 | 100 | 6 | 4 | 100 |
| 6 | 2 | 100 | 6 | 4 | 100 |
| 1 | 2 | 9 | 7 | 1 | 9 |
| 6 | 4 | 100 | 7 | 2 | 100 |
| 5 | 4 | 100 | 7 | 2 | 100 |
| 4 | 4 | 100 | 7 | 2 | 100 |
| 9 | 2 | 88 | 7 | 4 | 88 |
| 1 | 2 | 9 | 8 | 1 | 9 |
| 6 | 4 | 100 | 8 | 2 | 100 |
| 5 | 4 | 100 | 8 | 2 | 100 |
| 4 | 4 | 100 | 8 | 2 | 100 |
| 9 | 2 | 88 | 8 | 4 | 88 |
| 8 | 4 | 88 | 9 | 2 | 88 |
| 7 | 4 | 88 | 9 | 2 | 88 |
| 3 | 1 | 77 | 10 | 2 | 77 |
| 13 | 3 | 87 | 10 | 4 | 87 |
| 3 | 3 | 87 | 10 | 4 | 87 |
| 3 | 1 | 77 | 11 | 2 | 77 |
| 13 | 3 | 87 | 11 | 4 | 87 |
| 3 | 3 | 87 | 11 | 4 | 87 |
| 3 | 1 | 77 | 12 | 2 | 77 |
| 13 | 3 | 87 | 12 | 4 | 87 |
| 3 | 3 | 87 | 12 | 4 | 87 |
| 12 | 4 | 87 | 13 | 3 | 87 |
| 11 | 4 | 87 | 13 | 3 | 87 |
| 10 | 4 | 87 | 13 | 3 | 87 |
+------------+-----------+-----+------------+-----------+-----+