多表查询
- 多表关系
- 多表查询概述
- 内连接
- 外连接
- 自连接
- 子查询
- 多表查询案例
多表关系
- 概述
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:
一对多(多对一)
- 案例:部门 与 员工的关系
- 关系:一个部门对应多个员工,一个员工对应一个部门
- 实现:在多的一方建立外键,指向一的一方的主键
多对多
- 案例:学生 与 课程的关系
- 关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
- 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
| create table student ( |
| id int auto_increment primary key comment '主键ID', |
| name varchar(10) comment '姓名', |
| no varchar(10) comment '学号' |
| ) comment '学生表'; |
| insert into student values (null, '黛绮丝', '2000100101'), (null, '谢逊', '2000100102'), (null, '殷天正', '2000100103'), (null, '韦一笑', '2000100104'); |
| |
| create table course ( |
| id int auto_increment primary key comment '主键ID', |
| name varchar(10) comment '课程名称' |
| ) comment '课程表'; |
| insert into course values (null, 'Java'), (null, 'PHP'), (null, 'MySQL'), (null, 'Hadoop'); |
| |
| create table student_course ( |
| id int auto_increment comment '主键' primary key , |
| studentid int not null comment '学生ID', |
| courseid int not null comment '课程ID', |
| constraint fk_courseid foreign key (courseid) references course(id), |
| constraint fk_studentid foreign key (studentid) references student(id) |
| ) comment '学生课程中间表'; |
| insert into student_course values (null, 1, 1), (null, 1, 2), (null, 1, 3), (null, 2, 2), (null, 2, 3), (null, 3, 4); |
一对一
- 案例:用户 与 用户详情的关系
- 关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
- 实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
| create table tb_user |
| ( |
| id int auto_increment primary key comment '主键ID', |
| name varchar(10) comment '姓名', |
| age int comment '年龄', |
| gender char(1) comment '1: 男, 2: 女', |
| phone char(11) comment '手机号' |
| )comment '用户基本信息表'; |
| |
| create table tb_user_edu ( |
| id int auto_increment primary key comment '主键ID', |
| degree varchar(20) comment '学历', |
| major varchar(50) comment '专业', |
| primaryschool varchar(50) comment '小学', |
| middleschool varchar(50) comment '中学', |
| university varchar(50) comment '大学', |
| userid int unique comment '用户ID', |
| constraint fk_userid foreign key (userid) references tb_user(id) |
| ) comment '用户教育信息表'; |
| |
| insert into tb_user (id, name, age, gender, phone) values |
| (null, '黄渤', 45, '1', '18800001111'), |
| (null, '冰冰', 35, '2', '18800002222'), |
| (null, '码云', 55, '1', '18800008888'), |
| (null, '李彦宏', 50, '1', '18800009999'); |
| |
| insert into tb_user_edu (id, degree, major, primaryschool, middleschool, university, userid) values |
| (null, '本科', '舞蹈', '静安区第一小学', '静安区第一中学', '北京舞蹈学院', 1), |
| (null, '硕士', '表演', '朝阳区第一小学', '朝阳区第一中学', '北京电影学院', 2), |
| (null, '本科', '英语', '杭州市第一小学', '杭州市第一中学', '杭州师范大学', 3), |
| (null, '本科', '应用数学', '阳泉第一小学', '阳泉区第一中学', '清华大学', 4); |
多表查询概述
- 概述:指从多张表中查询数据
- 笛卡尔积:笛卡尔乘积是指在数学中,两个集合 A集合 和 B集合 的所有组合情况。(在多表查询时,需要消除无效的笛卡尔积)

表内数据
| create table dept( |
| id int auto_increment comment 'ID' primary key, |
| name varchar(50) not null comment '部门名称' |
| )comment '部门表'; |
| |
| create table emp( |
| id int auto_increment comment 'ID' primary key, |
| 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 '员工表'; |
| |
| |
| alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id); |
| |
| INSERT INTO dept (id, name) VALUES (1, '研发部'), (2, '市场部'),(3, '财务部'), (4, '销售部'), (5, '总经办'), (6, '人事部'); |
| INSERT INTO emp (id, name, age, job,salary, entrydate, managerid, dept_id) VALUES |
| (1, '金庸', 66, '总裁',20000, '2000-01-01', null,5), |
| |
| (2, '张无忌', 20, '项目经理',12500, '2005-12-05', 1,1), |
| (3, '杨逍', 33, '开发', 8400,'2000-11-03', 2,1), |
| (4, '韦一笑', 48, '开发',11000, '2002-02-05', 2,1), |
| (5, '常遇春', 43, '开发',10500, '2004-09-07', 3,1), |
| (6, '小昭', 19, '程序员鼓励师',6600, '2004-10-12', 2,1), |
| |
| (7, '灭绝', 60, '财务总监',8500, '2002-09-12', 1,3), |
| (8, '周芷若', 19, '会计',48000, '2006-06-02', 7,3), |
| (9, '丁敏君', 23, '出纳',5250, '2009-05-13', 7,3), |
| |
| (10, '赵敏', 20, '市场部总监',12500, '2004-10-12', 1,2), |
| (11, '鹿杖客', 56, '职员',3750, '2006-10-03', 10,2), |
| (12, '鹤笔翁', 19, '职员',3750, '2007-05-09', 10,2), |
| (13, '方东白', 19, '职员',5500, '2009-02-12', 10,2), |
| |
| (14, '张三丰', 88, '销售总监',14000, '2004-10-12', 1,4), |
| (15, '俞莲舟', 38, '销售',4600, '2004-10-12', 14,4), |
| (16, '宋远桥', 40, '销售',4600, '2004-10-12', 14,4), |
| (17, '陈友谅', 42, null,2000, '2011-10-12', 1,null); |
| |
| select * from emp, dept ; |
| select * from emp, dept where emp.dept_id = dept.id; |
多表查询分类
连接查询
- 内连接:相当于查询 A、B 交集部分数据
- 外连接:
- 左外连接:查询左表所有数据,以及两张表交集部分数据
- 右外连接:查询右表所有数据,以及两张表交集部分数据
- 自连接:当前表与自身的连接查询,自连接必须使用表别名
子查询
内连接
- 内连接查询的是两张表交集的部分

内连接查询语法
隐式内连接
| SELECT 字段列表 FROM 表1, 表2 WHERE 条件...; |
显式内连接
| SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 连接条件...; |
案例
| |
| |
| |
| select emp.name, dept.name from emp, dept where emp.dept_id = dept.id; |
| |
| select e.name, d.name from emp as e, dept as d where e.dept_id = d.id; |
| |
| |
| |
| |
| select e.name, d.name from emp as e inner join dept as d on e.dept_id = d.id; |
外连接
外连接查询语法
左外连接
| SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件 ...; |
- 相当于查询 表1(左表)的所有数据 包含 表1 和 表2 交集部分的数据
右外连接
| SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件 ...; |
- 相当于查询 表2(右表)的所有数据 包含 表1 和 表2 交集部分的数据
案例
| |
| |
| |
| select e.*, d.name from emp as e left outer join dept as d on e.dept_id = d.id; |
| select e.*, d.name from emp as e left join dept as d on e.dept_id = d.id; |
| |
| |
| |
| select d.*, e.* from emp as e right outer join dept as d on e.dept_id = d.id; |
| select d.*, e.* from dept as d left outer join emp as e on d.id = e.dept_id; |
自连接
自连接查询语法
| SELECT 字段列表 FROM 表A 别名 A JOIN 表A 别名 B ON 条件; |
自连接查询,可以是内连接查询,也可以是外连接查询。
案例
| |
| |
| |
| select a.name, b.name from emp as a, emp as b where a.managerid = b.id; |
| select a.name, b.name from emp as a inner join emp as b on a.managerid = b.id; |
| |
| |
| select a.name as '员工', b.name as '领导' from emp as a left outer join emp as b on a.managerid = b.id; |
联合查询-union,union all
- 对于 union 查询,就是把多次查询的结果合并起来,形成一个新的查询结果集
语法
| SELECT 字段列表 FROM 表A ... |
| UNION[ALL] |
| SELECT 字段列表 FROM 表B ...; |
- 对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致
- union all 会将全部的数据直接合并在一起,union 会对合并之后的数据去重
案例
| |
| |
| select * from emp where salary < 5000 |
| union all |
| select * from emp where age > 50; |
| |
| select * from emp where salary < 5000 |
| union |
| select * from emp where age > 50; |
子查询
- 概念:SQL 语句中嵌套 SELECT 语句,称为嵌套查询,又称子查询
| SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2); |
子查询外部的语句可以是 INSERT/UODATE/DELETE/SELECT的任何一个
标量子查询
- 子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询称为标量子查询
- 常用的操作符:
=
<>
>
>=
<
<=
案例
| |
| |
| |
| select id from dept where name = '销售部'; |
| |
| select * from emp where dept_id = 4; |
| |
| select * from emp where dept_id = (select id from dept where name = '销售部'); |
| |
| |
| |
| select entrydate from emp where name = '方东白'; |
| |
| select * from emp where entrydate > '2009-02-12'; |
| |
| select * from emp where entrydate > (select entrydate from emp where name = '方东白'); |
列子查询
- 列子查询:子查询返回的结果是一列(可以是多行),这种子查询称为列子查询
- 常用的操作符:IN、NOT IN、ANY、SOME、ALL
| 操作符 描述 |
| IN 在指定的集合范围之内,多选一 |
| NOT IN 不在指定的集合范围之内 |
| ANY 子查询返回列表中,有任意一个满足即可 |
| SOME 与 ANY 等同,使用 SOME 的地方都可以使用 ANY |
| ALL 子查询返回列表的所有指都必须满足 |
案例
| |
| |
| |
| |
| select id from dept where name = '销售部' or name = '市场部'; |
| |
| select * from emp where dept_id in (2, 4); |
| |
| select * from emp where dept_id in (select id from dept where name = '销售部' or name = '市场部'); |
| |
| |
| |
| select salary from emp where dept_id in (select id from dept where name = '财务部'); |
| |
| |
| select * from emp where salary > all (select salary from emp where dept_id in (select id from dept where name = '财务部')); |
| |
| |
| |
| select salary from emp where dept_id = (select id from dept where name = '研发部'); |
| |
| select * from emp where salary > any (select salary from emp where dept_id = (select id from dept where name = '研发部')); |
| select * from emp where salary > some (select salary from emp where dept_id = (select id from dept where name = '研发部')); |
行子查询
- 子查询返回的结果是一行(可以是多列),这种子查询称为行子查询
- 常用的操作符:
=
<>
IN
NOT IN
案例
| |
| |
| |
| select salary, managerid from emp where name = '张无忌'; |
| |
| |
| select * from emp where (salary, managerid) = (select salary, managerid from emp where name = '张无忌'); |
表子查询
- 子查询返回的结果是多行多列,这种子查询称为表子查询
- 常用的操作符 IN
案例
| |
| |
| |
| select job, salary from emp where name = '鹿杖客' or name = '宋远桥'; |
| |
| |
| select * from emp where (job, salary) in (select job, salary from emp where name = '鹿杖客' or name = '宋远桥'); |
| |
| |
| |
| |
| select * from emp where entrydate > '2006-01-01'; |
| |
| |
| select * from (select * from emp where entrydate > '2006-01-01') as e left outer join dept as d on e.dept_id = d.id; |
多表查询案例
| |
| create table salgrade( |
| grade int, |
| losal int, |
| hisal int |
| ) comment '薪资等级表'; |
| |
| insert into salgrade values (1,0,3000); |
| insert into salgrade values (2,3001,5000); |
| insert into salgrade values (3,5001,8000); |
| insert into salgrade values (4,8001,10000); |
| insert into salgrade values (5,10001,15000); |
| insert into salgrade values (6,15001,20000); |
| insert into salgrade values (7,20001,25000); |
| insert into salgrade values (8,25001,30000); |
| |
| |
| |
| |
| |
| select emp.name, emp.age, emp.job, dept.name from emp, dept where emp.dept_id = dept.id; |
| |
| |
| |
| |
| |
| select e.name, e.age, e.job, d.name from emp as e inner join dept as d on e.dept_id = d.id where e.age < 30; |
| |
| |
| |
| |
| |
| select d.id, d.name from dept as d where d.id in (select e.dept_id from emp as e); |
| select distinct d.id, d.name from dept as d inner join emp as e on d.id = e.dept_id; |
| |
| |
| |
| |
| |
| select e.*, d.name from emp as e left outer join dept as d on e.dept_id = d.id where e.age > 40; |
| |
| |
| |
| |
| |
| select e.*, s.grade, s.losal, s.hisal from emp as e inner join salgrade as s on e.salary >= s.losal and e.salary <= s.hisal; |
| select e.*, s.grade, s.losal, s.hisal from emp as e, salgrade as s where e.salary >= s.losal and e.salary <= s.hisal; |
| |
| |
| |
| |
| |
| |
| select e.*, s.grade |
| from emp as e |
| inner join dept as d on e.dept_id = d.id |
| inner join salgrade as s on e.salary between s.losal and s.hisal |
| where d.name = '研发部'; |
| |
| select e.*, s.grade from emp as e, dept as d, salgrade as s where e.dept_id = d.id and d.name = '研发部' and e.salary between s.losal and s.hisal; |
| |
| |
| |
| |
| |
| select avg(t.salary) |
| from (select emp.salary |
| from emp |
| inner join dept on emp.dept_id = dept.id |
| where dept.name = '研发部') as t; |
| |
| select avg(e.salary) |
| from emp e, |
| dept d |
| where e.dept_id = d.id |
| and d.name = '研发部'; |
| |
| |
| |
| select e.salary from emp as e where e.name = '灭绝'; |
| |
| select * |
| from emp as e |
| where e.salary > (select e.salary |
| from emp as e |
| where e.name = '灭绝'); |
| |
| |
| |
| select avg(e.salary) |
| from emp as e ; |
| |
| select * |
| from emp as e |
| where e.salary > (select avg(e.salary) |
| from emp as e); |
| |
| |
| |
| select avg(e.salary) |
| from emp as e |
| where e.dept_id = 1; |
| |
| select * |
| from emp as e |
| where e.salary < (select avg(ee.salary) |
| from emp as ee |
| where ee.dept_id = e.dept_id); |
| |
| |
| select d.*, |
| (select count(*) |
| from emp as e |
| where e.dept_id = d.id) as '总人数' |
| from dept as d; |
| |
| |
| |
| |
| |
| select s.name, |
| s.no, |
| c.name |
| from student as s, |
| course as c, |
| student_course as sc |
| where s.id = sc.studentid |
| and c.id = sc.courseid; |
| |
| select s.name, |
| s.no, |
| c.name |
| from student as s |
| inner join student_course sc on s.id = sc.studentid |
| inner join course c on sc.courseid = c.id; |
| |
事务
事务简介
- 事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败
默认 MySQL 的事务是自动提交的,也就是说,当执行一条 DML 语句,MySQL 会立即隐式的提交事务
事务操作
| |
| create table account( |
| id int auto_increment primary key comment '主键ID', |
| name varchar(10) comment '姓名', |
| money int comment '余额' |
| ) comment '账户表'; |
| insert into account(id, name, money) VALUES (null,'张三',2000),(null,'李四',2000); |
| |
| |
| |
| update account set money = 2000 where name = '张三' or name = '李四'; |
抛出异常第三条语句不执行,结果 1000 2000
| |
| |
| select * from account where name = '张三'; |
| |
| |
| update account set money = money - 1000 where name = '张三'; |
| |
| 程序抛出异常... |
| |
| update account set money = money + 1000 where name = '李四'; |
查看/设置事务提交方式
| SELECT @@autocommit; |
| SET @@autocommit = 0; |
提交事务
回滚事务
案例
| select @@autocommit; |
| set @@autocommit = 0; |
| |
| |
| |
| select * from account where name = '张三'; |
| |
| |
| update account set money = money - 1000 where name = '张三'; |
| |
| |
| update account set money = money + 1000 where name = '李四'; |
| |
| |
| commit; |
| |
| |
| rollback; |
开启事务
| START TRANSACTION 或 BEGIN; |
提交事务
回滚事务
案例
| |
| start transaction ; |
| |
| |
| |
| select * from account where name = '张三'; |
| |
| |
| update account set money = money - 1000 where name = '张三'; |
| |
| 程序执行错误... |
| |
| update account set money = money + 1000 where name = '李四'; |
| |
| |
| commit; |
| |
| |
| rollback; |
事务四大特性(ACID)
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中数据的改变就是永久的。
并发事务问题
问题 |
描述 |
脏读 |
一个事务读到另外一个事务还没提交的数据。 |
不可重复读 |
一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读 |
幻读 |
一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了“幻影” |
事务隔离级别
隔离级别 |
脏读 |
不可重复读 |
幻读 |
Read uncommitted |
√ |
√ |
√ |
Read committed |
x |
√ |
√ |
Repeatable Read(默认) |
x |
x |
√ |
Serializable |
x |
x |
x |
注意:事务隔离级别越高,数据越安全,但是性能越低
| |
| SELECT @@TRANSACTION_ISOLATION; |
| |
| |
| SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} |
| |
| |
| select @@transaction_isolation; |
| |
| |
| set session transaction isolation level read uncommitted ; |
| |
| set session transaction isolation level repeatable read ; |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构