MySQL数据库SQL语句进阶篇——连接查询与子查询详解
MySQL数据库SQL语句进阶篇——连接查询与子查询详解
前言
在MySQL数据库学习中,掌握基础的SELECT、INSERT、UPDATE、DELETE语句后,连接查询和子查询是进阶学习的重要内容。本文将通过40道经典练习题,深入解析连接查询和子查询的各种应用场景,帮助读者掌握复杂的SQL查询技巧。
数据库表结构回顾
部门表(dept)
CREATE TABLE dept (DEPTNO int(11) NOT NULL COMMENT '部门编号',DNAME varchar(20) NOT NULL COMMENT '部门名称',LOC varchar(20) COMMENT '部门所在的位置',PRIMARY KEY (DEPTNO)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '部门表';
员工表(emp)
CREATE TABLE emp (EMPNO int(11) NOT NULL COMMENT '雇员的编号',ENAME varchar(50) NOT NULL COMMENT '雇员的姓名',JOB varchar(50) NOT NULL COMMENT '职位',MGR int(11) NULL DEFAULT NULL COMMENT '雇员对应的领导编号',HIREDATE date NULL DEFAULT NULL COMMENT '雇员的雇佣日期',SAL decimal(7, 2) NULL DEFAULT NULL COMMENT '基本工资',COMM decimal(7, 2) NULL DEFAULT NULL COMMENT '奖金,佣金',DEPTNO int(11) NULL DEFAULT NULL COMMENT '雇员所在的部门编号',PRIMARY KEY (EMPNO),CONSTRAINT FK_EMP_DEPTNO FOREIGN KEY (DEPTNO) REFERENCES dept (DEPTNO)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '员工表';
工资等级表(salgrade)
CREATE TABLE salgrade (GRADE int(11) NOT NULL COMMENT '工资的等级 主键',LOSAL decimal(7, 2) NOT NULL COMMENT '此等级的最低工资',HISAL decimal(7, 2) NOT NULL COMMENT '此等级的最高工资',PRIMARY KEY (GRADE)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '工资等级表';
测试数据
部门表数据
-- ----------------------------
-- 部门表插入数据
-- ----------------------------
INSERT INTO DEPT VALUES (10, '财务部', '武汉');
INSERT INTO DEPT VALUES (20, '研发部', '武汉');
INSERT INTO DEPT VALUES (30, '销售部', '深圳');
INSERT INTO DEPT VALUES (40, '业务部', '上海');
员工表数据
-- ----------------------------
-- 员工表插入数据
-- ----------------------------
INSERT INTO EMP values (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800.00, null, 20);
INSERT INTO EMP values (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600.00, 300.00, 30);
INSERT INTO EMP values (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250.00, 500.00, 30);
INSERT INTO EMP values (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975.00, null, 20);
INSERT INTO EMP values (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250.00, 1400.00, 30);
INSERT INTO EMP values (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850.00, null, 30);
INSERT INTO EMP values (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450.00, null, 10);
INSERT INTO EMP values (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000.00, null, 20);
INSERT INTO EMP values (7839, 'KING', 'PRESIDENT', null, '1981-11-17', 5000.00, null, 10);
INSERT INTO EMP values (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500.00, 0.00, 30);
INSERT INTO EMP values (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100.00, null, 20);
INSERT INTO EMP values (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950.00, null, 30);
INSERT INTO EMP values (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000.00, null, 20);
INSERT INTO EMP values (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300.00, null, 10);
工资等级表数据
-- ----------------------------
-- 工资等级表插入数据
-- ----------------------------
INSERT INTO SALGRADE (GRADE, LOSAL, HISAL) values (1, 700, 1200);
INSERT INTO SALGRADE (GRADE, LOSAL, HISAL) values (2, 1201, 1400);
INSERT INTO SALGRADE (GRADE, LOSAL, HISAL) values (3, 1401, 2000);
INSERT INTO SALGRADE (GRADE, LOSAL, HISAL) values (4, 2001, 3000);
INSERT INTO SALGRADE (GRADE, LOSAL, HISAL) values (5, 3001, 9999);
表关系说明
- emp表:员工信息表,包含员工的基本信息
- dept表:部门信息表,包含部门的基本信息
- salgrade表:工资等级表,用于工资等级划分
- 关联关系:emp表的DEPTNO字段关联dept表的DEPTNO字段
完整练习题集
1. 查询员工的基本信息,附加其上级的姓名(自关联)
-- 方法一:使用表别名进行自连接
SELECT e1.*, e2.ename AS manager_name
FROM emp e1, emp e2
WHERE e1.MGR = e2.empno;-- 方法二:使用INNER JOIN语法
SELECT e1.*, e2.ename AS manager_name
FROM emp e1
INNER JOIN emp e2 ON e1.MGR = e2.empno;
2. 显示工资比’ALLEN’高的所有员工的姓名和工作
SELECT ename, job
FROM emp
WHERE sal > (SELECT sal FROM emp WHERE ename = 'ALLEN');
3. 显示与scott从事相同工作的员工的信息(子查询)
SELECT * FROM emp
WHERE job = (SELECT job FROM emp WHERE ename = 'scott');
4. 显示销售部(‘销售部’)员工的姓名
-- 方法一:使用JOIN
SELECT e.ename
FROM emp e
JOIN dept d ON e.deptno = d.deptno
WHERE d.dname = '销售部';-- 方法二:使用子查询
SELECT ename
FROM emp
WHERE deptno = (SELECT deptno FROM dept WHERE dname = '销售部');
5. 显示与30号部门’MARTIN’员工工资相同的员工的姓名和工资
SELECT ename, sal
FROM emp
WHERE sal = (SELECT sal FROM emp WHERE ename = 'MARTIN' AND deptno = 30);
6. 显示所有职员的姓名及其所在部门的名称和工资(表连接)
-- 方法一:使用INNER JOIN
SELECT e.ename, d.dname, e.sal
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno;-- 方法二:使用传统连接语法
SELECT e1.*, d1.dname
FROM emp e1, dept d1
WHERE e1.DEPTNO = d1.DEPTNO;
7. 查询在研发部工作人员的编号,姓名,工作部门,工作所在地
-- 方法一:使用INNER JOIN
SELECT e.empno, e.ename, d.dname, d.loc
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno
WHERE d.dname = '研发部';-- 方法二:使用传统连接语法
SELECT e.empno, e.ename, d.dname, d.loc
FROM emp e, dept d
WHERE e.DEPTNO = d.DEPTNO AND d.DNAME = '研发部';
8. 查询各个部门的名称和员工人数
-- 方法一:使用JOIN和GROUP BY
SELECT d.dname, COUNT(e.empno) AS 人数
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno
GROUP BY d.deptno, d.dname;-- 方法二:使用子查询
SELECT d1.dname, d.人数
FROM dept d1, (SELECT deptno, COUNT(empno) AS 人数 FROM emp GROUP BY deptno
) d
WHERE d1.deptno = d.deptno;
9. 查询工资相同的员工的工资和姓名(子查询)
-- 方法一:使用IN运算符
SELECT ename, sal
FROM emp
WHERE sal IN (SELECT sal FROM emp GROUP BY sal HAVING COUNT(*) > 1
)
ORDER BY sal, ename;-- 方法二:使用EXISTS子查询
SELECT e.ename, e.sal
FROM emp e
WHERE EXISTS (SELECT 1 FROM emp WHERE sal = e.sal GROUP BY sal HAVING COUNT(*) > 1
);
10. 查询工资最高的3名员工信息(排序)
SELECT * FROM emp
ORDER BY sal DESC
LIMIT 3;
11. 求入职日期相同的(年月日相同)的员工
-- 方法一:使用IN子查询
SELECT * FROM emp
WHERE hiredate IN (SELECT hiredate FROM emp GROUP BY hiredate HAVING COUNT(*) > 1
)
ORDER BY hiredate, ename;-- 方法二:使用EXISTS子查询
SELECT * FROM emp e
WHERE EXISTS (SELECT 1 FROM emp WHERE HIREDATE = e.HIREDATE GROUP BY HIREDATE HAVING COUNT(*) > 1
);
12. 查询每个员工的信息及工资级别,用到表(Salgrade)
-- 方法一:使用LEFT JOIN
SELECT e.*, s.grade
FROM emp e
LEFT JOIN salgrade s ON e.sal BETWEEN s.losal AND s.hisal;-- 方法二:使用传统连接语法
SELECT e.*, s.grade
FROM emp e, salgrade s
WHERE e.sal BETWEEN s.LOSAL AND s.HISAL;
13. 查询各个部门工资最高的员工信息
SELECT * FROM emp
WHERE sal IN (SELECT MAX(sal) FROM emp GROUP BY DEPTNO
);
14. 查询出有3个以上下属的员工信息(自关联)
SELECT * FROM emp e1
WHERE (SELECT COUNT(*)FROM emp WHERE e1.empno = mgr
) >= 3;
15. 查询所有大于本部平均工资的员工信息
SELECT * FROM emp e
WHERE e.sal > (SELECT FLOOR(AVG(sal)) FROM emp WHERE deptno = e.deptno
);
-- FLOOR(x) 返回不大于x的最大整数
16. 列出至少有一个雇员的所有部门信息(嵌套子查询)
SELECT d.*
FROM dept d, (SELECT DISTINCT e.deptno AS dno FROM emp e
) e
WHERE d.deptno = e.dno;
17. 列出薪金(工资)比’SMITH’多的所有雇员信息(嵌套子查询)
SELECT * FROM emp
WHERE sal > (SELECT sal FROM emp WHERE ename = 'SMITH');
18. 列出入职日期(雇佣日期)早于其直接上级的所有雇员(自表连接)
SELECT e1.*
FROM emp e1, emp e2
WHERE e1.mgr = e2.empno AND e1.hiredate < e2.hiredate;
19. 列出所有’CLERK’(办事员)的姓名及其部门名称
-- 方法一:使用INNER JOIN
SELECT e.ename, e.job, d.dname
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno
WHERE e.job = 'CLERK';-- 方法二:使用传统连接语法
SELECT e.ename, e.job, d.dname
FROM emp e, dept d
WHERE e.job = 'CLERK' AND e.DEPTNO = d.deptno;
20. 列出从事销售工作的雇员的姓名
-- 方法一:使用子查询
SELECT e.ename
FROM emp e
WHERE e.deptno = (SELECT deptno FROM dept WHERE dname = '销售部');-- 方法二:使用连接查询
SELECT e.ename
FROM dept d, emp e
WHERE d.dname = '销售部' AND d.deptno = e.deptno;
21. 列出与"SCOTT"从事相同工作的所有雇员(嵌套自查询)
SELECT * FROM emp
WHERE job = (SELECT job FROM emp WHERE ename = 'SCOTT');
22. 列出薪金高于在部门30工作的所有雇员的姓名和薪金(嵌套子查询)
SELECT ename, sal
FROM emp
WHERE sal > ALL (SELECT sal FROM emp WHERE deptno = 30);
-- ALL 比较运算符
23. 列出从事同一种工作但属于不同的部门的雇员的不同组合
-- 方法一:使用CROSS JOIN(不推荐,性能差)
SELECT e.job, d.dname, d.deptno AS zuhe
FROM emp e
CROSS JOIN dept d;-- 方法二:使用自连接
SELECT e.*, d.deptno
FROM emp e, emp d
WHERE e.job = d.job AND e.deptno <> d.deptno AND e.ename < d.ename;
24. 查询每个部门工资最高的前两名
SELECT * FROM emp
WHERE sal IN (SELECT MAX(sal) FROM emp GROUP BY deptno
);
25. 查询工资相同的员工的工资和姓名
-- 方法一:使用IN子查询
SELECT ename, sal
FROM emp
WHERE sal IN (SELECT sal FROM emp GROUP BY sal HAVING COUNT(*) > 1
)
ORDER BY sal, ename;-- 方法二:使用自连接
SELECT e.sal, e.ename
FROM emp e, emp e1
WHERE e.sal = e1.sal AND e.empno <> e1.empno;
-- and后面是防止出现自己跟自己比的数据
26. 显示出和员工号7369部门相同的员工姓名,工资
SELECT ename, sal
FROM emp
WHERE deptno = (SELECT deptno FROM emp WHERE empno = 7369);
27. 列出至少有二个雇员的所有部门
SELECT d.*
FROM dept d
WHERE EXISTS (SELECT 1 FROM emp e WHERE e.deptno = d.deptno
);
28. 列出薪金比"SMITH"多的所有雇员
SELECT * FROM emp
WHERE sal > (SELECT sal FROM emp WHERE ename = 'SMITH');
29. 列出入职日期早于其直接上级的所有雇员
-- 方法一:使用传统连接语法
SELECT e.*
FROM emp e, emp m
WHERE e.mgr = m.empno AND e.hiredate < m.hiredate;-- 方法二:使用表别名
SELECT e1.*
FROM emp e1, emp e2
WHERE e1.mgr = e2.empno AND e1.hiredate < e2.hiredate;
30. 显示员工"KING"所管理的员工姓名
SELECT ename
FROM emp
WHERE mgr = (SELECT empno FROM emp WHERE ename = 'KING');
31. 显示比工资最高的员工参加工作时间晚的员工姓名,参加工作时间
SELECT ename, hiredate
FROM emp
WHERE hiredate > (SELECT hiredate FROM emp WHERE sal = (SELECT MAX(sal) FROM emp)
);
32. 查询职位和经理同员工SCOTT或BLAKE完全相同的员工姓名、职位
SELECT ename, job
FROM emp
WHERE (job, mgr) = (SELECT job, mgr FROM emp WHERE ename = 'SCOTT') AND ename NOT IN ('SCOTT')
UNION
SELECT ename, job
FROM emp
WHERE (job, mgr) = (SELECT job, mgr FROM emp WHERE ename = 'BLAKE') AND ename NOT IN ('BLAKE');
33. 查询工资高于编号为7782的员工工资,并且和7369号员工从事相同工作的员工的编号、姓名及工资
SELECT empno, ename, sal
FROM emp
WHERE sal > (SELECT sal FROM emp WHERE empno = 7782) AND job = (SELECT job FROM emp WHERE empno = 7369);
34. 查询大于自己部门平均工资的员工姓名,工资,所在部门平均工资,高于部门平均工资的额度
-- 方法一:使用相关子查询
SELECT e.ename, e.sal, (SELECT AVG(sal) FROM emp WHERE deptno = e.deptno) AS dept_avg_sal,(e.sal - (SELECT AVG(sal) FROM emp WHERE deptno = e.deptno)) AS diff_from_avg
FROM emp e
WHERE e.sal > (SELECT AVG(sal) FROM emp WHERE deptno = e.deptno);-- 方法二:使用连接查询
SELECT e1.ename, e1.sal, (e1.sal - e2.ag) AS '平均工资额度'
FROM emp e1, (SELECT deptno, AVG(sal) AS ag FROM emp GROUP BY deptno
) e2
WHERE e1.deptno = e2.deptno AND e1.sal > e2.ag;
35. 查询每个部门工资最高的员工信息
SELECT * FROM emp
WHERE sal IN (SELECT MAX(sal) FROM emp GROUP BY DEPTNO
);
36. 查询工资相同的员工的工资和姓名
SELECT ename, sal
FROM emp
WHERE sal IN (SELECT sal FROM emp GROUP BY sal HAVING COUNT(*) > 1
)
ORDER BY sal, ename;
37. 查询每个员工的信息及工资级别
SELECT e.*, s.grade
FROM emp e
LEFT JOIN salgrade s ON e.sal BETWEEN s.losal AND s.hisal;
38. 查询各个部门工资最高的员工信息
SELECT * FROM emp
WHERE sal IN (SELECT MAX(sal) FROM emp GROUP BY DEPTNO
);
39. 查询出有3个以上下属的员工信息
SELECT * FROM emp e1
WHERE (SELECT COUNT(*)FROM emp WHERE e1.empno = mgr
) >= 3;
40. 查询所有大于本部平均工资的员工信息
SELECT * FROM emp e
WHERE e.sal > (SELECT AVG(sal) FROM emp WHERE deptno = e.deptno
);
一、连接查询详解
1. 自连接查询
题目:查询员工的基本信息,附加其上级的姓名
-- 方法一:使用表别名进行自连接
SELECT e1.*, e2.ename AS manager_name
FROM emp e1, emp e2
WHERE e1.MGR = e2.empno;-- 方法二:使用INNER JOIN语法
SELECT e1.*, e2.ename AS manager_name
FROM emp e1
INNER JOIN emp e2 ON e1.MGR = e2.empno;
解析:
- 自连接是指同一个表与自身进行连接
- 通过表别名(e1, e2)区分不同的角色
- WHERE条件通过MGR字段关联上级信息
2. 内连接查询
题目:显示所有职员的姓名及其所在部门的名称和工资
-- 方法一:使用INNER JOIN
SELECT e.ename, d.dname, e.sal
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno;-- 方法二:使用传统连接语法
SELECT e1.*, d1.dname
FROM emp e1, dept d1
WHERE e1.DEPTNO = d1.DEPTNO;
解析:
- INNER JOIN只返回两个表中都存在的匹配记录
- 传统语法通过WHERE条件实现内连接效果
3. 多表连接查询
题目:查询在研发部工作人员的编号,姓名,工作部门,工作所在地
-- 方法一:使用INNER JOIN
SELECT e.empno, e.ename, d.dname, d.loc
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno
WHERE d.dname = '研发部';-- 方法二:使用传统连接语法
SELECT e.empno, e.ename, d.dname, d.loc
FROM emp e, dept d
WHERE e.DEPTNO = d.DEPTNO AND d.DNAME = '研发部';
二、子查询详解
1. 单行子查询
题目:显示工资比’ALLEN’高的所有员工的姓名和工作
SELECT ename, job
FROM emp
WHERE sal > (SELECT sal FROM emp WHERE ename = 'ALLEN');
解析:
- 子查询返回单个值(ALLEN的工资)
- 主查询使用比较运算符与子查询结果比较
2. 多行子查询
题目:查询工资相同的员工的工资和姓名
-- 方法一:使用IN运算符
SELECT ename, sal
FROM emp
WHERE sal IN (SELECT sal FROM emp GROUP BY sal HAVING COUNT(*) > 1
)
ORDER BY sal, ename;-- 方法二:使用EXISTS子查询
SELECT e.ename, e.sal
FROM emp e
WHERE EXISTS (SELECT 1 FROM emp WHERE sal = e.sal GROUP BY sal HAVING COUNT(*) > 1
);
解析:
- IN运算符用于检查值是否在子查询结果集中
- EXISTS用于检查子查询是否返回任何行
3. 相关子查询
题目:查询所有大于本部平均工资的员工信息
SELECT * FROM emp e
WHERE e.sal > (SELECT AVG(sal) FROM emp WHERE deptno = e.deptno
);
解析:
- 相关子查询中,子查询引用外部查询的列
- 每个员工都会与同部门的平均工资比较
三、高级查询技巧
1. 分组统计查询
题目:查询各个部门的名称和员工人数
-- 方法一:使用JOIN和GROUP BY
SELECT d.dname, COUNT(e.empno) AS 人数
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno
GROUP BY d.deptno, d.dname;-- 方法二:使用子查询
SELECT d1.dname, d.人数
FROM dept d1, (SELECT deptno, COUNT(empno) AS 人数 FROM emp GROUP BY deptno
) d
WHERE d1.deptno = d.deptno;
2. 排序和限制查询
题目:查询工资最高的3名员工信息
SELECT * FROM emp
ORDER BY sal DESC
LIMIT 3;
解析:
- ORDER BY用于排序,DESC表示降序
- LIMIT用于限制返回的行数
3. 复杂条件查询
题目:查询职位和经理同员工SCOTT或BLAKE完全相同的员工姓名、职位
SELECT ename, job
FROM emp
WHERE (job, mgr) = (SELECT job, mgr FROM emp WHERE ename = 'SCOTT') AND ename NOT IN ('SCOTT')
UNION
SELECT ename, job
FROM emp
WHERE (job, mgr) = (SELECT job, mgr FROM emp WHERE ename = 'BLAKE') AND ename NOT IN ('BLAKE');
解析:
- 使用元组比较(job, mgr)同时比较多个字段
- UNION用于合并两个查询结果
- NOT IN排除指定员工
四、实用查询技巧
1. 工资等级查询
题目:查询每个员工的信息及工资级别
SELECT e.*, s.grade
FROM emp e
LEFT JOIN salgrade s ON e.sal BETWEEN s.losal AND s.hisal;
解析:
- BETWEEN用于范围查询
- LEFT JOIN确保所有员工都会显示,即使没有匹配的工资等级
2. 部门最高工资查询
题目:查询各个部门工资最高的员工信息
SELECT * FROM emp
WHERE sal IN (SELECT MAX(sal) FROM emp GROUP BY DEPTNO
);
3. 下属数量查询
题目:查询出有3个以上下属的员工信息
SELECT * FROM emp e1
WHERE (SELECT COUNT(*)FROM emp WHERE e1.empno = mgr
) >= 3;
五、性能优化建议
1. 索引优化
- 在经常用于连接的字段上创建索引
- 在WHERE条件中使用的字段上创建索引
- 在ORDER BY和GROUP BY字段上创建索引
2. 查询优化
- 避免使用SELECT *,只查询需要的字段
- 合理使用LIMIT限制结果集大小
- 避免在WHERE子句中使用函数
3. 连接优化
- 优先使用INNER JOIN而不是WHERE连接
- 避免不必要的表连接
- 使用EXISTS代替IN(当子查询结果集较大时)
六、常见错误和注意事项
1. 子查询错误
-- 错误:子查询返回多行时使用比较运算符
SELECT * FROM emp WHERE sal > (SELECT sal FROM emp WHERE deptno = 30);-- 正确:使用ALL或ANY
SELECT * FROM emp WHERE sal > ALL (SELECT sal FROM emp WHERE deptno = 30);
2. 连接错误
-- 错误:忘记连接条件导致笛卡尔积
SELECT * FROM emp, dept;-- 正确:添加连接条件
SELECT * FROM emp, dept WHERE emp.deptno = dept.deptno;
3. 分组错误
-- 错误:SELECT中的非聚合字段未包含在GROUP BY中
SELECT deptno, ename, AVG(sal) FROM emp GROUP BY deptno;-- 正确:包含所有非聚合字段
SELECT deptno, ename, AVG(sal) FROM emp GROUP BY deptno, ename;
总结
通过这40道练习题,我们深入学习了MySQL中连接查询和子查询的各种应用场景。掌握这些技巧对于处理复杂的业务查询需求至关重要。在实际开发中,要根据具体的数据量和业务需求选择合适的查询方式,并注意查询性能的优化。
关键要点:
- 连接查询:掌握INNER JOIN、LEFT JOIN、自连接等
- 子查询:理解单行、多行、相关子查询的区别
- 性能优化:合理使用索引,避免不必要的连接
- 错误避免:注意常见的SQL语法错误和逻辑错误
通过不断练习和实践,相信您能够熟练运用这些SQL技巧解决各种复杂的数据库查询问题。