1. 多表关系
在设计数据库时,表与表之间常见的关系有三种:
|---------|-----------|--------------------|
| 关系 | 示例 | 实现方式 |
| 一对多 | 部门 → 员工 | 在多的一方建外键 → 一的一方主键 |
| 多对多 | 学生 ↔ 课程 | 建中间表,含两个外键分别指向两表主键 |
| 一对一 | 用户 ↔ 用户详情 | 任一方加外键并设 UNIQUE 约束 |
实战要点 :中间表通常用联合主键 PRIMARY KEY(student_id, course_id)。
2. 内连接(INNER JOIN)
内连接查询两表的交集部分。
2.1 隐式内连接
SELECT 字段 FROM 表1, 表2 WHERE 条件;
2.2 显式内连接(推荐)
SELECT 字段 FROM 表1 [INNER] JOIN 表2 ON 条件;
为什么推荐显式? 1. 语义更清晰 2. 优化器更容易识别 3. 标准 SQL,跨数据库兼容
-- 示例:查询员工姓名及所属部门名称
select e.name, d.name from employee e inner join dept d on e.dept = d.id;
3. 外连接(OUTER JOIN)
3.1 左外连接
查询左表全部数据 + 两表交集。
SELECT 字段 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件;
3.2 右外连接
查询右表全部数据 + 两表交集。
SELECT 字段 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件;
实践建议:日常开发以左外连接为主,右外连接可改写为左外(交换表顺序即可)。
3.3 内连接 vs 外连接
|-----|------------|----------------|
| 类型 | 返回 | 场景 |
| 内连接 | 两表匹配行 | 查有关联的数据 |
| 外连接 | 主表全部 + 匹配行 | 查"所有"数据(含无关联的) |
4. 自连接
表与自身进行连接。必须使用别名区分。
-- 查询员工及其直属领导的姓名
select a.name '员工', b.name '领导'
from employee a left join employee b on a.manager = b.id;
典型场景:组织架构(上级下级)、分类树(父子分类)、评论回复等。
5. 联合查询(UNION)
将多条查询结果合并为一条结果集。
SELECT ... FROM 表A
UNION [ALL]
SELECT ... FROM 表B;
|-----------|---------------|
| 关键字 | 说明 |
| UNION | 去重(额外排序,性能略低) |
| UNION ALL | 不去重(性能更高) |
联合查询 vs OR:UNION 不会导致索引失效,OR 一侧无索引则全索引失效。
UNION 使用条件:
- 多个 SELECT 的列数必须一致
- 对应列的数据类型必须兼容
6. 子查询
嵌套在 SQL 语句中的 SELECT 语句,外部可以是 INSERT / UPDATE / DELETE / SELECT。
6.1 按结果分类
|-----------|-----------|------------------|----------------|
| 分类 | 返回结果 | 常用操作符 | 示例 |
| 标量子查询 | 单个值(一行一列) | =、<>、>、< | 查销售部所有员工 |
| 列子查询 | 一列多行 | IN、ANY、ALL | 查销售部和市场部员工 |
| 行子查询 | 一行多列 | =、IN | 查与某人薪资和领导相同的员工 |
| 表子查询 | 多行多列 | IN、FROM 后 | 在 FROM 后作为临时表 |
6.2 列子查询操作符详解
|------------|-------------------------|
| 操作符 | 说明 |
| IN | 在集合中 |
| ANY / SOME | 满足任一即可(> ANY → 大于最小值) |
| ALL | 必须满足全部(> ALL → 大于最大值) |
6.3 实战示例
-- 标量子查询:查询销售部所有员工
select * from employee where dept = (select id from dept where name='销售部');
-- 列子查询:查询销售部和市场部员工
select * from employee where dept in (select id from dept where name in ('销售部','市场部'));
-- 行子查询:查询与 "张三" 薪资和直属领导相同的员工
select * from employee where (salary, manager) = (select salary, manager from employee where name='张三');
-- 表子查询:查询 2006 年后入职员工及部门(子查询作为临时表)
select e.*, d.* from (select * from employee where entrydate>'2006-01-01') e
left join dept d on e.dept = d.id;