1. MySQL复合查询实战指南:从入门到精通
作为一名数据库开发工程师,我经常遇到需要从多个表中提取数据的场景。单表查询虽然简单,但在实际业务中远远不够用。今天我就来分享MySQL复合查询的核心技巧,这些内容都是我多年实战经验的总结。
复合查询主要包括多表查询、自连接、子查询和合并查询四大类。掌握这些技能,你就能应对90%以上的复杂查询需求。下面我会用大量实战案例,带你深入理解每种查询的用法和注意事项。
2. 多表查询:跨表关联的艺术
2.1 理解多表查询的本质
多表查询的核心是通过"关联字段"将多个表的数据连接起来。最常见的关联方式是通过主键和外键建立关系。比如员工表(emp)中的deptno字段关联到部门表(dept)的deptno字段。
sql复制-- 基础多表查询语法
select 表1.字段, 表2.字段
from 表1, 表2
where 表1.关联字段 = 表2.关联字段;
2.2 实战案例解析
案例1:员工与部门信息关联
sql复制select e.ename, e.sal, d.dname
from emp e, dept d
where e.deptno = d.deptno;
这个查询获取每个员工的姓名、工资和所在部门名称。注意我们给表起了别名(e和d),这样写起来更方便。
案例2:多条件筛选
sql复制select e.ename, e.sal, d.dname
from emp e, dept d
where e.deptno = d.deptno
and d.deptno = 10
and e.sal > 2000;
这里我们增加了两个筛选条件:只查询10号部门,且工资大于2000的员工。
案例3:三表关联
sql复制select e.ename, e.sal, s.grade
from emp e, salgrade s
where e.sal between s.losal and s.hisal;
这个查询将员工表与薪资等级表关联,找出每个员工的薪资等级。
2.3 性能优化技巧
-
索引优化:确保关联字段上有索引,可以大幅提升查询速度。比如在emp.deptno和dept.deptno上建立索引。
-
减少查询字段:只select需要的字段,避免使用"select *"。
-
合理使用表别名:给表起简短的别名,既提高可读性又减少输入量。
注意:多表查询必须要有WHERE条件,否则会产生笛卡尔积,导致结果集爆炸式增长。
3. 自连接:表与自身的对话
3.1 自连接的应用场景
自连接是一种特殊的多表查询,它把同一张表当作两张表来使用。最常见的场景是查询员工与其上级的关系,因为上级也是员工,信息都存储在emp表中。
3.2 实战案例
sql复制select worker.ename as 员工, leader.ename as 领导
from emp worker, emp leader
where worker.mgr = leader.empno;
这个查询找出每个员工及其直接领导的姓名。关键点在于:
- 给emp表起了两个别名:worker和leader
- 通过worker.mgr = leader.empno建立关联
3.3 高级应用:多层组织结构查询
sql复制select w.ename as 员工, l1.ename as 直接领导, l2.ename as 领导的领导
from emp w, emp l1, emp l2
where w.mgr = l1.empno
and l1.mgr = l2.empno;
这个查询展示了如何通过自连接查询多层组织结构。
4. 子查询:查询中的查询
4.1 子查询分类
子查询可以按返回结果分为:
- 单行子查询:返回一行一列
- 多行子查询:返回多行一列
- 多列子查询:返回多行多列
按位置可以分为:
- WHERE子句中的子查询
- FROM子句中的子查询
- SELECT子句中的子查询
4.2 WHERE子句中的子查询
单行子查询
sql复制select * from emp
where sal > (select avg(sal) from emp);
找出工资高于平均工资的员工。
多行子查询
sql复制select * from emp
where deptno in (select deptno from dept where loc = 'NEW YORK');
找出在纽约工作的所有员工。
多列子查询
sql复制select * from emp
where (deptno, job) = (select deptno, job from emp where ename = 'SMITH');
找出与SMITH部门和职位相同的所有员工。
4.3 FROM子句中的子查询
sql复制select e.ename, e.sal, d.avg_sal
from emp e, (select deptno, avg(sal) as avg_sal from emp group by deptno) d
where e.deptno = d.deptno
and e.sal > d.avg_sal;
这个查询找出工资高于本部门平均工资的员工。子查询先计算每个部门的平均工资,然后在主查询中与员工表关联。
4.4 子查询性能优化
- 尽量将子查询转化为连接查询,通常性能更好
- 对子查询结果集建立临时索引
- 避免在子查询中使用SELECT *
- 考虑使用WITH子句(CTE)替代复杂子查询
5. 合并查询:UNION的妙用
5.1 UNION与UNION ALL的区别
- UNION:合并结果并去重
- UNION ALL:合并结果不去重,性能更好
5.2 实战案例
sql复制-- 查询工资大于2500或职位是MANAGER的员工(去重)
select ename, sal, job from emp where sal > 2500
union
select ename, sal, job from emp where job = 'MANAGER';
-- 同样的查询但不去重
select ename, sal, job from emp where sal > 2500
union all
select ename, sal, job from emp where job = 'MANAGER';
5.3 使用注意事项
- 合并的查询必须有相同数量的列
- 对应列的数据类型必须兼容
- 结果集的列名取自第一个SELECT语句
- 可以使用ORDER BY对最终结果排序
6. 实战OJ真题解析
6.1 查找所有员工入职时的薪水情况
sql复制select e.emp_no, s.salary
from employees e, salaries s
where e.emp_no = s.emp_no
and s.from_date = e.hire_date
order by e.emp_no desc;
6.2 获取所有非manager的员工
sql复制select emp_no from employees
where emp_no not in (select emp_no from dept_manager);
6.3 获取所有员工当前的manager
sql复制select e.emp_no, d.emp_no as manager
from dept_emp e, dept_manager d
where e.dept_no = d.dept_no
and e.emp_no != d.emp_no;
7. 性能优化与避坑指南
7.1 常见性能问题
- 笛卡尔积:忘记加关联条件导致的结果集爆炸
- 索引缺失:关联字段没有索引导致全表扫描
- 子查询嵌套过深:多层嵌套子查询性能急剧下降
- 不必要的数据传输:SELECT * 查询不需要的列
7.2 优化建议
- 使用EXPLAIN分析查询执行计划
- 为常用查询条件创建合适的索引
- 考虑使用JOIN替代子查询
- 限制返回的数据量(使用LIMIT)
- 对大表考虑分区或分表策略
7.3 避坑要点
- 多表查询必须要有WHERE条件
- 子查询要考虑返回结果的行数和列数
- UNION ALL比UNION性能更好,但不去重
- 自连接必须使用表别名
- 注意NULL值处理,特别是NOT IN子查询
8. 实际项目中的应用经验
在我参与的一个大型ERP系统中,复合查询的应用非常广泛。这里分享几个实际案例:
- 组织架构图:使用自连接查询多层汇报关系
- 销售分析报表:多表关联查询销售、客户、产品信息
- 权限控制:使用子查询确定用户可见的数据范围
- 数据汇总:使用UNION合并多个分公司的销售数据
通过这些实际应用,我总结了几个关键点:
- 复杂的查询可以拆分成多个简单查询,用临时表存储中间结果
- 在应用层处理某些逻辑可能比用SQL更高效
- 定期优化和重构SQL语句是必要的
- 理解业务需求比写出复杂的SQL更重要
最后,建议大家在掌握这些技巧后,多在实际项目中练习。SQL是一种实践性很强的技能,只有通过不断的实战才能真正掌握。