目录
- 多表查询
- 多表关系
- 多表查询概述
- 多表查询的分类
- 内连接
- 外连接
- 自连接
- 联合查询union,union all
- 子查询
- 标量子查询
- 列子查询
- 行子查询
- 表子查询
- 综合练习
- 小结
- 事务
- 事务简介
- 事务的操作
- 四大特性ACID
- 并发事务问题
- 事务的隔离级别
- 小结
多表查询
之前的SQL语句里的DQL只能进行单表查询,不能多表查询
多表关系
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,表与表之间的关系大致有以下几种:
- 一对多(或多对一)
- 多对多
- 多对一
-
一对多(多对一)
在一对多(多对一)的关联关系中,比较典型的就是例如:部门与员工之间的关系
关系:一个部门对应多个员工,一个员工对应一个部门
实现:在多的一方建立外键,指向单个的一方的主键
例如:在员工的一方建立外键,指向部门的主键
-
多对多
在多对多的关联关系中,比较典型的就是例如:学生与课程的关系
关系:一个学生可以选修多个课程,一门课程也可以供多个学生选择
实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
例如:在学生表和课程表之间创建一个中间表(学生课程关系表),这个中间表有两个外键,分别对应学生表的主键和课程表的主键
use onesql;
create table student(id int primary key auto_increment comment 'ID',name varchar(10) not null comment '姓名',no varchar(10) comment '学号'
)comment '学生表';create table course(id int primary key auto_increment comment 'ID',name varchar(10) comment '课程'
)comment '课程表';create table stu_cou(id int primary key auto_increment comment 'ID',stu_id int comment '学生id',cou_id int comment '课程号'
)comment '学生课程关系表';insert into course(name)values ('java'),('高数'),('大物'),('c++');
insert into student(name,no)values ('jack',001),('tom',002),
('rose',003),('jams',004);
insert into stu_cou(stu_id, cou_id) values (1,1),(1,2),(2,1),(2,3),
(3,3),(3,4),(4,1),(4,3);alter table stu_cou add constraint stuId foreign key (stu_id)
references student(id);
alter table stu_cou add constraint couId foreign key (cou_id)
references course(id);
datagrip的图形化界面
在中间表打开图
-
一对一
在一对一的关联关系中,比较典型的就是例如:用户和用户的详细信息
关系:一对一关系多用于单表的拆分,将一张表的基础字段放在一张表中,其他的详情字段放在另一张表中,以提升操作效率
实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE–unique)
例如:下图
create table tb_user(id int primary key auto_increment comment 'ID',name varchar(10) comment '姓名',age int comment '年龄',gender char(1) comment '1:nan 2:nv',phone char(11) comment '手机号'
)comment '基本信息';create table tb_user_edu(id int primary key auto_increment comment 'ID',degree varchar(20) comment '学历',major varchar(50) comment '专业',school1 varchar(50) comment '小学',school2 varchar(50) comment '中学',school3 varchar(50) comment '大学',userid int unique comment '用户id',constraint fk_userid foreign key (userid)references tb_user(id)
)comment '基本信息';-- 下面原本还应该有插入数据,但是太繁琐,所以没写
多表查询概述
指从多张表中查询数据
语法:
-- 直接这样查询就会使表1.表2的数据全部相乘
-- 例如,表1为员工表,有10个员工(10条数据),表2为部门表,有6个部门(6条数据),
-- 那么直接使用下面的方式查询出的结果就会有60条数据,也就是所谓的笛卡尔积
select 字段列表 from 表1,表2...;
笛卡尔积:笛卡尔乘积是指在数学中,两个集合A和B的所有组合情况,
所以,在进行多表查询时需要消除无效的笛卡尔积,实际的消除方法就使用where条件,使多表中关联的外键进行联系。
例如:还是表1为员工表,有10个员工(10条数据),表2为部门表,有6个部门(6条数据),在表1中有一个外键字段dept_id与部门表的主键(id)相关联,在进行多表查询时,就可以在where条件中加上 dept_id与部门表的主键(id)相等这一条件,这样就可以消除多余的笛卡尔积;
select * from 表1,表2 where 表1.dept_id=表2.id;
这样执行后就不会有多余的笛卡尔积
多表查询的分类
-
多表查询主要分为两类:
连接查询
和子查询
-
连接查询
连接查询又分为内连接,外连接和自连接内连接:相当于查询A,B交集部分的数据
外连接:左外连接:查询左表所有数据,以及两张表交集部分数据右外连接:查询右表所有数据,以及两张表交集部分数据
自连接:当前表与自身的连接查询,自连接必须使用表别名
内连接
内连接查询的是两张表交集部分的数据
-
内连接查询语法:
隐式内连接:
SELECT 字段列表 FROM 表1,表2 WHERE 条件...; select 字段列表 from 表1,表2 where 条件...;
显式内连接:
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 连接条件...; select 字段列表 from 表1 [inner] join 表2 on 连接条件...;
演示:
create table department(id int primary key auto_increment comment 'ID',dept_name varchar(8) comment '部门'
)comment '部门表';
insert into department(dept_name)values ('主管部'),('营销部'),('软件部'),('硬件部'),('财政部');alter table emp add constraint fk_deptTwo_id foreign key (dept_id)references department(id);drop table emp;
create table emp(id int comment '编号',workno varchar(10) comment '工号',name varchar(10) comment '姓名',gender char(1) comment '性别',age tinyint comment '年龄',idCard char(18) comment '身份证号',workAddress varchar(50) comment '工作地址',entryDate date comment '入职日期',dept_id int comment '部门号',constraint fk_dp_id foreign key (dept_id)references department(id)
)comment '员工信息表';
insert into emp values
(1,'1','张三','男',20,'12345678901234567','北京','2000-01-01',1),
(2,'2','张四','女',25,'12345678901234567','北京南','2000-02-01',3),
(3,'3','李四','女',35,'12345678901234567','北京北','2000-03-01',5),
(4,'4','李1','女',32,'12345678901234567','北京','2000-04-01',2),
(5,'5','李92','男',21,'12345678901234567','北北','2000-05-01',1),
(6,'6','李3','女',22,'12345678901234567','京北','2000-06-01',4),
(7,'7','李4s','男',23,'12345678901234567','北','2000-07-01',4),
(8,'8','李5','女',35,'12345678901234567','北京北','2000-08-01',5),
(9,'9','李61','男',37,'12345678901234567','北京1北','2000-09-01',1),
(10,'10','李67','女',35,'12345678901234567','北2京北','2000-10-01',2),
(11,'11','李7','男',37,'12345678901234567','北京北','2000-11-01',2),
(12,'12','李12','女',25,'12345678901234567','北京是北','2000-09-01',3),
(13,'13','李21','男',15,'12345678901234567','北是京北','2000-03-01',4),
(14,'14','李123','女',25,'12345678901234567','北上述京北','2000-01-01',3),
(15,'15','李32','男',88,'12345678901234567','北京北说','2000-02-03',1),
(16,'16','李19','男',95,null,'北京北2','2000-09-12',2);//隐式内连接
select *from emp,department where department.id=emp.dept_id;
//显式内连接
select *from emp inner join department on department.id=emp.dept_id;
外连接
外连接分为左外连接和右外连接
这里on后面的条件是外键和主键之间的条件,如果是筛选的条件的话,还需要再写在where后面
- 左外连接
查询表1的所有数据,包括表1和表2交集的数据(这里的交集就是相关联的数据,像是外键那样)
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件...WHERE...;
select 字段列表 from 表1 left [outer] join 表2 on 条件...where...;
- 右外连接
查询表2的所有数据,包括表1和表2交集的数据(这里的交集就是相关联的数据,像是外键那样)
SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件...WHERE...;
select 字段列表 from 表1 right [outer] join 表2 on 条件...where...;
例子:
-- 查询emp表的所有数据,和对应的部门信息(左外连接)
select * from emp left join department d on emp.dept_id = d.id;
-- 查询department表的所有数据,和对应的员工信息(右外连接)
select d.*,e.* from emp e right join department d on e.dept_id = d.id;
自连接
语法:
SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件...;
select 字段列表 from 表A 别名A join 表A 别名B on 条件...;
自连接查询,可以是内连接查,也可以是外连接查
即自连接也可以使用左外或右外的形式
SELECT 字段列表 FROM 表A 别名A LEFT/RIGHT JOIN 表A 别名B ON 条件...;
select 字段列表 from 表A 别名A left/right join 表A 别名B on 条件...;
自连接中表必须起别名
自连接的作用主要是作用于一张表上的关系,查询同一张表上的不同行的信息,例如,一张员工表上有两个字段,第一个字段是id
,第二个字段是领导id
,使用自连接就可以将领导id与对应的id连接起来
联合查询union,union all
对于联合查询,就是把多次查询的结果合并起来,形成一个新的查询结果集
语法:
SELECT 字段列表 FROM 表A ...
UNION[ALL]
SELECT 字段列表 FROM 表B...;
-- 这段程序会将上面对表A的查询结果和对表B的查询结果合并起来,形成一个新的查询结果集
案例:
-- 1.将workno小于等于10的员工和年龄大于30的员工全部查询出来
select * from emp where workno<=10
union
select *from emp where age>=30;
-- union后不跟all,重复的数据就不会显示,如果加上all,重复的数据就会显示
注意:联合查询时,多张表的列数需要保持一致,所以要求查询的字段列表是一致的。
子查询
概念:SQL语句中嵌套SELECT语句,称为嵌套查询,又称子查询
语法:
SELECT * FROM t1 WHERE column1=(SELECT column1 FROM t2);
select *from t1 where column1 =(select column1 from t2);
-- 括号内部的语句就成为子查询语句,
-- 子查询外部的语句,即=左边的语句,可以是SQL语句的任意形式
-- 例如 INSERT/UPDATE/DELETE/SELECT的任何一个
-
根据子查询的结果不同,可以将子查询分为:
1.标量子查询(子查询结果为单个值)2.列子查询(子查询结果为一列)
3.行子查询(子查询结果为一行)
4.表子查询(子查询结果为多行多列)
根据子查询出现的位置,可以将子查询分为:WHERE(where)之后,FROM(from)之后,SELECT(select)之后的子查询
标量子查询
概念:
标量子查询返回的结果是单个值(数字,字符串,日期等),最简单的子查询形式
- 标量子查询常用的操作符:
=
,<>
,>
,>=
,<
,<=
练习:
-- 1.查询软件部的所有员工信息
-- 拆解成两步,先查询软件部的部门id,再通过部门id查询详细的员工信息
select id from department where dept_name='软件部';-- 3
select * from emp where dept_id=3;
-- 合并成一步就是
-- 这里子查询的结果是3,只有一条记录,所以是标量子查询
select *from emp where dept_id=(select id from department where dept_name='软件部');
列子查询
列子查询返回的结果是一列(可以是多行);
-
列子查询常用的操作符
IN(in)
,NOT IN(not in)
,ANY(any)
,SOME(some)
,ALL(all)
-
操作符的作用:
案例:
-- 1.查询营销部和硬件部的所有员工信息
-- 还是分成两步,第一步就是查询'硬件部','营销部'对应的部门id,通过部门id去寻找相应的员工
-- 由于这里子查询的结果是一列数据,所以是列子查询
select *from emp where dept_id in
(select id from department where dept_name in('硬件部','营销部'));-- 查询大于所有财政部年龄的员工的信息
select *from emp where age>all(select age from emp where dept_id=(select id from department where dept_name='财政部'));select *from emp where age>any(select age from emp where dept_id=(select id from department where dept_name='主管部'));
行子查询
行子查询返回的结果是一行(可以是多列)
语法:
select 字段列表 from 表名 where(条件字段1,条件字段2,。。。)=(子查询)
- 行子查询常用的操作符:
=
,<
,>
,IN(in)
,NOT IN(not in)
-- 查询与张三年龄和住址相同的员工信息
select *from emp where
(age,workAddress)=(select age,workAddress from emp where name='张三');
表子查询
表子查询返回的结果是多行多列
- 常用的操作符
IN(in)
案例:
-- 查询与张三和李四年龄和住址相同的员工信息
select *from emp where
(age,workAddress)
in
(select age,workAddress from emp where name ='张三'or name='李四');
综合练习
-- 1.
select name,age,d.dept_name from emp e,department d where e.dept_id=d.id;
-- 2.
select name,age,d.dept_name from emp e,department d where (e.dept_id=d.id)and e.age<30;
-- 3.(内连接)
select distinct d.id,d.dept_name from emp e,department d where e.dept_id =d.id;
-- 4.
select e.*,d.dept_name from emp e left join department d on e.dept_id=d.id where age>30;
-- 5.
select e.name,s.grade from salgrade s,emp e where e.gongzi>=s.low and e.gongzi<s.highest;
-- 6.
select e.*,s.grade from salgrade s,emp ewhere e.dept_id=(select id from department where dept_name='研发部')and(e.gongzi>=s.low and e.gongzi<s.highest);
-- 7.
select avg(e.gongzi) from salgrade s,emp ewhere e.dept_id=(select id from department where dept_name='研发部')and(e.gongzi>=s.low and e.gongzi<s.highest);
-- 8.
select *from emp where gongzi>(select gongzi from emp where name='李7');
-- 9.
select *from emp where gongzi>(select avg(gongzi) from emp);
-- 10.
select *from emp e2 where e2.gongzi<(select avg(e1.gongzi)from emp e1 where e2.dept_id=e1.dept_id);
-- 11.
select d.id,d.dept_name,(select count(*)from emp e where d.id=e.dept_id) '数量' from department d;
小结
事务
事务简介
-
事务:
是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败
例如:银行的转账,中间不能有任何异常出现,一旦出现异常就必须要赊销之前的所有操作
在MySQL中的事务是自动提交的,也就是说,当执行一条sql语句,MySQL会立即隐式的提交事务,不用自已手动提交
事务的操作
修改事务的提交方式
-
查看/设置事务的提交方式
-- 查看事务的自动提交方式。如果为1,就是自动提交,如果为0,就是手动提 交 SELECT @@autocommit; -- 可以通过set将系统的自动提交方式改为手动 SET @@autocommit=0;-- select @@autocommit; set @@autocommit=0;
-
提交事务
COMMIT;commit;
-
回滚事务(将事务返回到初始状态)
ROLLBACK;rollback;
如果设置为手动提交,当sql语句执行完成后,数据库里的数据不会改变,必须手动执行提交指令(commit)后才会改变;
当sql语句出现异常时,就不用执行提交事务的指令了,而是执行回滚指令(rollback),这样数据就不会丢失了
案例:
create table account( id int auto_increment primary key comment 'ID', name varchar(10) comment '姓名', money int comment '储蓄' )comment '账户表'; insert into account (name, money) values ('zhangsan',1000),('lisi',2000); -- 恢复数据 update account set money=2000 where name='zhangsan' or name='lisi';-- 转账 -- 1.查询zhangsan余额 -- 2.将zhangsan余额-1000 -- 3.给lisi余额+1000set @@autocommit=0;-- 现在下面的三条sql语句分别是三条单独的事务 select money from account where name='zhangsan'; update account set money =money-1000 where name='zhangsan'; update account set money =money+1000 where name='lisi'; commit ; rollback ;
不修改事务的提交方式
-
开启事务(在想要将一段sql语句缩成一个事务时,将开启事务放在这段sql语句的上面)
START TRANSACTION 或 BEGIN; start transaction 或 begin;
-
提交事务
COMMIT;commit;
-
回滚事务(将事务返回到初始状态)
ROLLBACK;rollback;
start transaction ;-- 开启事务,将开启事务与下面三条程序一起运行 select money from account where name='zhangsan'; update account set money =money-1000 where name='zhangsan'; update account set money =money+1000 where name='lisi'; -- 当上面开启事务后,如果不提交事务,数据库是不会有任何变动的 commit ; rollback ;
四大特性ACID
-
事务有四大特性:
1.原子性(Atomicity)
2.一致性(Consistency)
3.隔离性(Isolation)
4.持久性(Durability)
并发事务问题
并发事务问题指的是:A事务和B事务同时操作一个数据库或一张表的时候,所引发的一些问题
-
常见的并发事务问题
脏读:一个事务读到另一个事务还没有提交的数据
例如:在事务隔离级别不够的情况下,现在有两个并发的事务同时操作同一张表,B事务修改表的数据,在B事务还没有提交的时候,A事务又读取这个表,此时A事务就会读到被修改后的数据,就称之为脏读不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读
例如:在Read committed的事务隔离级别下,有两个并发的事务同时操作同一张表,A事务先读取数据1,此时数据1为1000,然后B事务修改数据1为0,在B事务提交后,A事务再次进行读取数据1,此时读取出来的数据1是0,事务A读取两次,两次的数据不同,这就是不可重复读幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了“幻影”。
例如:在Repeatable Read的事务隔离级别下,有两个并发的事务同时操作同一张表,事务A先读取一个表中不存在的值(比如说id=3的一个数据),然后事务B执行插入SQL,将id=3的数据插入表中,B事务提交后,A事务再将插入id=3数据插入表中就会报错,显示数据在表中已经存在,此时事务A再进行读取id=3的数据,还是显示数据不存在,这就是幻读
事务的隔离级别
事务的隔离级别就是来解决事务的并发问题的
数据库中的事务隔离级别
四种事务隔离级别:(从上到下事务的隔离级别越来越高,性能越来越差)
1.Read uncommitted:在这个隔离级别下,上面三个并发的事务问题都有可能会出现(性能高,数据安全性差)
2.Read committed(oracle数据库的默认事务隔离级别):解决了脏读问题,但是不可重复读和幻读还是会出现
3.Repeatable Read(MySqL的默认的事务隔离级别):幻读问题会出现
4.Serializable:解决了所有的并发事务问题(性能低,数据安全性强)
- 事务隔离级别的语法:
-- 查看事务隔离级别 SELECT @@TRANSACTION_ISOLATION;select @@transaction_isolation;-- 设置事务隔离级别 SET [SESSION | GLOBAL]TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}set [session(只对当前客户端窗口有效) | global(对所有的客户端窗口有效)]transaction isolation level {read uncommitted | read committed | repeatable read | serializable}
练习:
-- 查看事务的隔离级别
select @@transaction_isolation;
-- 设置事务的隔离级别
set session transaction isolation level read committed ;
set session transaction isolation level repeatable read ;
注意:事务的隔离级别越高,数据越安全,但是数据库的性能越低