SQL-多表查询
一、MySQL 中的多表查询
多表查询是指在一个 SQL 查询语句中同时涉及两个或多个数据表,通过表之间的关联关系(通常是外键)获取跨表的数据。
核心问题:笛卡尔积当直接查询多个表而不添加关联条件时,会产生 “笛卡尔积” 现象 —— 即第一个表的每一行与第二个表的每一行强制组合,结果集的行数是两个表行数的乘积(如 A 表 3 行、B 表 4 行,结果为 12 行)。这会导致数据冗余且无意义,因此多表查询必须通过关联条件(通常是where
或on
子句)消除笛卡尔积。
二、代码详细讲解
1. 创建user
表及操作
create table user(id int primary key auto_increment comment '主键',name varchar(10) not null unique comment '姓名',age int check (age >= 0 and age <= 120) comment '年龄',status char(1) default '1' comment '状态',gender char(1) comment '性别'
)comment '用户表';
- 作用:创建名为
user
的用户表。 - 字段说明:
id
:整数类型,主键(primary key
),自增(auto_increment
),唯一标识每条记录。name
:长度 10 的字符串,非空(not null
)且唯一(unique
),确保姓名不重复。age
:整数,通过check
约束限制年龄在 0-120 之间(注意:MySQL 8.0.16 + 才真正支持check
约束,低版本仅语法校验不生效)。status
:长度 1 的字符,默认值为 '1'(如可表示 “启用” 状态)。gender
:长度 1 的字符,存储性别(如 ' 男'/' 女 ')。
select * from user;
- 作用:查询
user
表中所有字段的所有记录(初始为空,后续插入数据后可见结果)。
insert into user(name, age, status, gender) values
('Tom1', 19, '0', '男'),
('Tom2', 25, '1', '女'),
('Tom3', 17, '0', '男');
- 作用:向
user
表插入 3 条记录,指定name
、age
、status
、gender
字段的值。 - 注意:
id
是自增主键,无需手动插入,会自动生成 1、2、3。
insert into user(name, age, gender) values ('Tom5', 32,'男');
- 作用:向
user
表插入第 4 条记录,仅指定name
、age
、gender
。 - 注意:
status
字段未指定,将使用默认值 '1'(定义表时设置的default '1'
)。
2. 创建dept
表及操作
create table dept(id int primary key auto_increment comment 'ID' ,name varchar(50) not null comment '部门名称'
)comment '部门表';
- 作用:创建名为
dept
的部门表,存储部门信息。 - 字段说明:
id
:自增主键,部门唯一标识。name
:部门名称,非空(确保每个部门有名称)。
insert into dept values (1, '研发部'),(2,'市场部'),(3, '财务部'),(4, '销售部'),(5, '总经办');
- 作用:插入 5 条部门记录,手动指定
id
(1-5)和对应部门名称。 - 注意:虽然
id
是自增主键,但此处手动插入值后,后续自增会从 6 开始。
select * from dept;
- 作用:查询
dept
表所有记录,验证插入的部门数据。
3. 创建emp
表及操作
create table emp(id int primary key auto_increment comment 'ID' ,name varchar(50) not null comment '姓名',age int comment '年龄',job varchar(20) comment '职位',salary int comment '薪资',entrydate date comment '入职时间',managerid int comment '直属领导ID',dept_id int comment '部门ID'
)comment '员工表';
- 作用:创建名为
emp
的员工表,存储员工信息。 - 字段说明:
managerid
:关联自身表的id
(表示员工的直属领导)。dept_id
:关联dept
表的id
(表示员工所属部门)。
insert into emp values
(1, '金庸', 66, '总裁', 20000, '2000-01-01', null, 5),
(2, '张无忌', 20, '项目经理', 12500, '2005-12-01', 1, 1),
(3, '杨晓', 33, '开发', 8400, '2003-01-01', 2, 1),
(4, '韦一笑', 66, '开发', 11000, '2002-02-02', 2, 1),
(5, '常遇春', 43, '开发', 10500, '2004-09-05', 3, 1),
(6, '小昭', 19, '文员', 6000, '2004-10-14', 2, 1);
- 作用:插入 6 条员工记录,包含姓名、年龄、职位等信息。
- 说明:
managerid
为null
表示 “金庸” 没有上级(总裁)。dept_id
对应dept
表的id
(如dept_id=1
表示属于 “研发部”)。
4. 外键约束操作
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
- 作用:给
emp
表的dept_id
添加外键约束,关联dept
表的id
。 - 说明:
- 外键约束确保
emp.dept_id
的值必须是dept.id
中已存在的值(或null
),防止无效的部门关联。 fk_emp_dept_id
是约束的名称(自定义,便于后续操作)。
- 外键约束确保
alter table emp drop foreign key fk_emp_dept_id;
- 作用:删除
emp
表中名为fk_emp_dept_id
的外键约束。 - 注意:删除外键后,
emp.dept_id
可以插入dept.id
中不存在的值(可能导致数据不一致,需谨慎)。
select * from emp;
- 作用:查询
emp
表所有记录,验证员工数据。
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on UPDATE cascade on DELETE cascade ;
- 作用:重新添加外键约束,并设置级联更新和级联删除。
- 说明:
on update cascade
:当dept.id
更新时,emp.dept_id
自动同步更新。on delete cascade
:当dept
表中某部门被删除时,emp
表中关联该部门的记录也会被删除。
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update set null on delete set null ;
- 作用:修改外键约束为 “级联设空”。
- 说明:
on update set null
:当dept.id
更新时,emp.dept_id
被设为null
。on delete set null
:当dept
表中某部门被删除时,emp
表中关联该部门的dept_id
被设为null
。
5. 多表查询操作
select * from emp, dept where emp.dept_id = dept.id;
- 作用:查询
emp
和dept
表的关联数据,通过where
条件消除笛卡尔积。 - 说明:
- 若不加
where emp.dept_id = dept.id
,会产生笛卡尔积(emp
6 行 ×dept
5 行 = 30 行无效数据)。 - 加条件后,只保留
emp.dept_id
与dept.id
匹配的记录(即员工所属部门存在的记录)。
- 若不加
-- 隐式内连接
select emp.name, dept.name from emp, dept where emp.dept_id = dept.id;
- 作用:隐式内连接查询,只获取两表交集(员工和其所属部门均存在的记录)。
- 说明:
- 隐式内连接用逗号分隔表,通过
where
指定关联条件。 - 只查询
emp.name
(员工姓名)和dept.name
(部门名称),避免返回冗余字段。
- 隐式内连接用逗号分隔表,通过
-- 显式内连接
select emp.name, dept.name from emp inner join dept on emp.dept_id = dept.id;
- 作用:显式内连接查询,功能与隐式内连接一致(查询两表交集)。
- 说明:
- 显式内连接用
inner join
关键字,关联条件用on
子句(推荐,更清晰)。 inner
可省略,即select ... from emp join dept on ...
。
- 显式内连接用
-- 左外连接
select emp.*, d.name from emp left outer join dept d on emp.dept_id = d.id;
- 作用:左外连接查询,返回左表(
emp
)所有记录,以及右表(dept
)中匹配的记录(右表无匹配则显示null
)。 - 说明:
left outer join
中outer
可省略,简写为left join
。dept d
给dept
表起别名d
,简化查询语句。- 即使某员工的
dept_id
为null
(无部门),也会被查询出来。
-- 右外连接
select d.name,emp.* from emp right join dept d on emp.dept_id = d.id;
- 作用:右外连接查询,返回右表(
dept
)所有记录,以及左表(emp
)中匹配的记录(左表无匹配则显示null
)。 - 说明:
- 例如
dept
表中有 “市场部”(id=2),但emp
表中无该部门员工,则结果中 “市场部” 对应的员工字段为null
。 - 右外连接可通过交换表位置转为左外连接(如
dept left join emp on ...
)。
- 例如
-- 自连接(查询员工及其领导姓名)
select 员工表.name, 领导表.name from emp 员工表, emp 领导表 where 员工表.managerid = 领导表.id;
- 作用:自连接查询(表与自身连接),获取员工及其直属领导的姓名。
- 说明:
- 需给表起别名(
员工表
和领导表
)区分同一表的不同角色。 - 关联条件
员工表.managerid = 领导表.id
:员工的领导 ID 对应另一员工的 ID。 - 结果中不会包含 “金庸”(因
managerid
为null
,无领导)。
- 需给表起别名(
-- 自连接(包含无领导的员工)
select 员工表.name, 领导表.name from emp 员工表 left join emp 领导表 on 员工表.managerid = 领导表.id;
- 作用:左外自连接,查询所有员工及其领导姓名(包括无领导的员工)。
- 说明:
- “金庸” 的
managerid
为null
,左连接会保留该记录,领导表.name
显示null
。
- “金庸” 的
三、关键注意事项
- 外键约束:关联字段类型必须一致(如
emp.dept_id
和dept.id
均为int
);外键可设为null
(表示无关联)。 - 笛卡尔积:多表查询必须加关联条件(
where
或on
),否则结果无意义。 - 连接类型:内连接只取交集,外连接保留主表所有记录,自连接用于表内层级关系(如员工 - 领导)。
- 表别名:多表查询(尤其是自连接)需用别名简化语句,避免字段歧义(如
emp.name
vsdept.name
)。