回顾
1. DQL单表高级查询条件 where比较运算逻辑运算符in关键字between关键字if null关键字like关键字% 多个任意字符_ 单个任意字符排序 order byascdesc聚合函数count(*) 统计行,包括null值maxminavgsum分组 group by 分组字段 having 分组后条件过滤分页 limit 开始索引,每页显示个数公式:索引=(当前页-1)× 每页的个数
2. 数据库约束主键约束create table 表名(id int primary key auto_increment,....);唯一 unique非空 not null默认值 default3. 表关系一对多主表从表(外键字段 指向 主表主键)多对多二个主表创建第三张作为从表(二个外键字段 分别指向 各自主表的主键),由多个一对多组成一对一外键是唯一主键是外键外键约束保证多表有关的数据进行限定,保证他的正确性,有效性和完整性[constraint] [约束性] foreign key(外键字段) references 主表(主键)4. 数据库备份与还原
MySQL多表查询与事务
今日目标
1. DQL多表高级查询语法笛卡尔积内连接外连接子查询2. 多表综合案例(四张表),学习多表查询的规律3. 事务安全(TCL)转账案例引入它,解决场景4. 表的范式(数据库的设计规则)
一 多表查询【重点】
同时查询多张表获取到需要的数据组成完整的信息返回给用户。
准备数据
-- 多表查询
create database web03_1;
use web03_1;
-- 创建部门表(主表)
CREATE TABLE dept (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20)
);INSERT INTO dept (NAME) VALUES ('开发部'),('市场部'),('财务部'),('销售部');-- 创建员工表(从表)
CREATE TABLE emp (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(10),gender CHAR(1), -- 性别(sex)salary DOUBLE, -- 工资join_date DATE, -- 入职日期dept_id INT -- 外键字段
);INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('孙悟空','男',7200,'2013-02-24',1);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('猪八戒','男',3600,'2010-12-02',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('唐僧','男',9000,'2008-08-08',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('白骨精','女',5000,'2015-10-07',3);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('蜘蛛精','女',4500,'2011-03-14',1);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('沙僧','男',6666,'2017-03-04',null);
1.1 笛卡尔积
* 功能二张表的记录所有字段进行了组合,这种现象称为笛卡尔积,(又称为交叉连接)
* 语法SELECT ... FROM 左表,右表;
1.2 内连接
* 功能拿左表的记录去匹配右表的记录,若匹配上就显示,匹配不上就不显示(二张表的交集)
* 语法1.隐式内连接【了解】SELECT ... FROM 左表,右表 where 条件过滤;2.显示内连接【推荐】 select ... from 左表 [inner] join 右表 on 条件过滤;
-- 隐式内连接
SELECT * FROM emp e,dept d WHERE e.dept_id = d.id;
-- 显示内连接
SELECT * FROM emp e INNER JOIN dept d ON e.dept_id = d.id;# 内连接
-- 查询唐僧的 id,姓名,性别,工资和所在部门名称
SELECT e.id,e.name,e.gender,e.salary,d.name FROM emp e INNER JOIN dept d ON e.dept_id = d.id WHERE e.name = '唐僧';
1.3 外连接
1. 左外连接【掌握】功能展示左表的全部记录,去匹配右表的记录,若匹配上显示数据,若没匹配上显示NULL语法select ... from 左表 left [outer] join 右表 on 条件过滤;2. 右外连接【了解】功能展示右表的全部记录,去匹配左表的记录,若匹配上显示数据,若没匹配上显示NULL语法select ... from 左表 right [outer] join 右表 on 条件过滤;
# 左外连接
-- 查询所有员工信息(左边)及对应的部门名称(右边)
SELECT * FROM emp e LEFT OUTER JOIN dept d ON e.dept_id = d.id;
-- 查询所有部门(左边)及对应的员工信息(右边)
SELECT * FROM dept d LEFT OUTER JOIN emp e ON e.dept_id = d.id;# 右外连接
-- 查询所有部门(右边)及对应的员工信息(左边)
SELECT * FROM emp e RIGHT OUTER JOIN dept d ON d.id = e.dept_id;
1.4 子查询
* 功能一条select语句执行结果,作为另一条select 语法 的一部分,使用需要添加括号
* 语法1. 单值SELECT MAX(salary) FROM emp;2. 单列多行SELECT NAME FROM emp;3. 多列多行SELECT * FROM emp;* 小结子查询结果为单列,作为条件在where后使用select ... from 表名 where 字段 = or in(子查询);子查询结果为多列,作为虚拟表在form后使用select ... from (子查询) as 表别名....
# 子查询# 子查询结果为单值
-- 1 查询工资最高的员工是谁?
SELECT * FROM emp WHERE salary = (SELECT MAX(salary) FROM emp);
-- 2 查询工资小于平均工资的员工有哪些?
-- 2.1 先求出平均工资
SELECT AVG(salary) FROM emp;
-- 2.2 再查询小于平均工资的员工
SELECT * FROM emp WHERE salary < (SELECT AVG(salary) FROM emp);# 子查询结果为单列多行
-- 1 查询工资大于5000的员工,来自于哪些部门的名字
-- 1.1 查询工资大于5000的员工
SELECT dept_id FROM emp WHERE salary > 5000;
-- 1.2 来自于哪些部门的名字
SELECT * FROM dept WHERE id IN(SELECT dept_id FROM emp WHERE salary > 5000);-- 2 查询开发部与财务部所有的员工信息
-- 2.1 查询开发部与财务部
SELECT id FROM dept WHERE NAME IN('开发部','财务部');
-- 2.2 对应的的员工信息
SELECT * FROM emp WHERE dept_id IN(SELECT id FROM dept WHERE NAME IN('开发部','财务部'));# 子查询结果为多列多行
-- 1 查询出2011年以后入职的员工信息,包括部门名称
-- 1.1 查询出2011年以后入职的员工信息
SELECT * FROM emp WHERE join_date > '2011-1-1';
-- 1.2 作为虚拟表连接部门表
SELECT * FROM (SELECT * FROM emp WHERE join_date > '2011-1-1') AS e LEFT JOIN dept d ON e.dept_id = d.id;-- 扩展
SELECT * FROM emp e LEFT OUTER JOIN dept d ON e.dept_id = d.id WHERE e.join_date > '2011-1-1';
二 多表案例【四张表】
我们在企业开发时,根据不同的业务需求往往需要通过2张及以上的表中去查询需要的数据。其实不管是几张表的查询,都是有规律可循的。
准备数据
-- 多表案例
create database web03_2;
use web03_2;
-- 部门表
CREATE TABLE dept (id INT PRIMARY KEY auto_increment, -- 部门iddname VARCHAR(50), -- 部门名称loc VARCHAR(50) -- 部门位置
);-- 添加4个部门
INSERT INTO dept(id,dname,loc) VALUES
(10,'教研部','北京'),
(20,'学工部','上海'),
(30,'销售部','广州'),
(40,'财务部','深圳');-- 职务表
CREATE TABLE job (id INT PRIMARY KEY,jname VARCHAR(20), -- 职务名称description VARCHAR(50) -- 职务描述
);-- 添加4个职务
INSERT INTO job (id, jname, description) VALUES
(1, '董事长', '管理整个公司,接单'),
(2, '经理', '管理部门员工'),
(3, '销售员', '向客人推销产品'),
(4, '文员', '使用办公软件');-- 员工表
CREATE TABLE emp (id INT PRIMARY KEY, -- 员工idename VARCHAR(50), -- 员工姓名job_id INT, -- 职务id 外键mgr INT , -- 上级领导joindate DATE, -- 入职日期salary DECIMAL(7,2), -- 工资 99999.99bonus DECIMAL(7,2), -- 奖金 99999.99dept_id INT, -- 所在部门编号 外键CONSTRAINT emp_jobid_ref_job_id_fk FOREIGN KEY (job_id) REFERENCES job (id),CONSTRAINT emp_deptid_ref_dept_id_fk FOREIGN KEY (dept_id) REFERENCES dept (id)
);-- 添加员工
INSERT INTO emp(id,ename,job_id,mgr,joindate,salary,bonus,dept_id) VALUES
(1001,'孙悟空',4,1004,'2000-12-17','8000.00',NULL,20),
(1002,'卢俊义',3,1006,'2001-02-20','16000.00','3000.00',30),
(1003,'林冲',3,1006,'2001-02-22','12500.00','5000.00',30),
(1004,'唐僧',2,1009,'2001-04-02','29750.00',NULL,20),
(1005,'李逵',4,1006,'2001-09-28','12500.00','14000.00',30),
(1006,'宋江',2,1009,'2001-05-01','28500.00',NULL,30),
(1007,'刘备',2,1009,'2001-09-01','24500.00',NULL,10),
(1008,'猪八戒',4,1004,'2007-04-19','30000.00',NULL,20),
(1009,'罗贯中',1,NULL,'2001-11-17','50000.00',NULL,10),
(1010,'吴用',3,1006,'2001-09-08','15000.00','0.00',30),
(1011,'沙僧',4,1004,'2007-05-23','11000.00',NULL,20),
(1012,'李逵',4,1006,'2001-12-03','9500.00',NULL,30),
(1013,'小白龙',4,1004,'2001-12-03','30000.00',NULL,20),
(1014,'关羽',4,1007,'2002-01-23','13000.00',NULL,10);-- 工资等级表
CREATE TABLE salarygrade (grade INT PRIMARY KEY, -- 等级losalary INT, -- 最低工资hisalary INT -- 最高工资
);-- 添加5个工资等级
INSERT INTO salarygrade(grade,losalary,hisalary) VALUES
(1,7000,12000),
(2,12010,14000),
(3,14010,20000),
(4,20010,30000),
(5,30010,99990);
练习
-- 1 查询所有员工信息。显示员工编号,员工姓名,工资,职务名称,职务描述
-- 1.1 确定查询哪些表?【员工 职务】
SELECT * FROM emp e INNER JOIN job j ;
-- 1.2 确定连接条件?【员工(job_id) 指向 职务(id)】
SELECT * FROM emp e INNER JOIN job j ON e.job_id = j.id;
-- 1.3 确定显示字段?【员工编号,员工姓名,工资,职务名称,职务描述】
SELECT e.id,e.ename, e.salary,j.jname,j.descriptionFROM emp e INNER JOIN job j ON e.job_id = j.id;
-- 2 查询所有员工信息。显示员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
-- 2.1 确定查询的表?【员工、职务、部门】
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d;
-- 2.2 确定连接条件?【员工(job_id) 指向 职务(id)、员工(dept_id) 指向 部门(id)】
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept dON e.job_id = j.id AND e.dept_id = d.id; -- 了解SELECT * FROM emp e INNER JOIN job j ON e.job_id = j.idINNER JOIN dept d ON e.dept_id = d.id; -- 推荐
-- 2.3 确定显示字段?【显示员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置】
SELECT e.id,e.ename, e.salary,j.jname,j.description,d.dname,d.locFROM emp e INNER JOIN job j ON e.job_id = j.idINNER JOIN dept d ON e.dept_id = d.id; -- 推荐
-- 3 查询所有员工信息。显示员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
-- 3.1 确定查询表?【员工、职务、部门、工资等级】
SELECT * FROM emp eINNER JOIN job jINNER JOIN dept dINNER JOIN salarygrade sg;
-- 3.2 确定连接条件?【员工(job_id) 指向 职务(id)、员工(dept_id) 指向 部门(id)、员工(salary) 范围查找 对应工资等级】
SELECT * FROM emp eINNER JOIN job j ON e.job_id = j.idINNER JOIN dept d ON e.dept_id = d.idINNER JOIN salarygrade sg ON e.salary BETWEEN sg.losalary AND sg.hisalary;
-- 3.3 确定显示字段?【显示员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级】
SELECT e.id,e.ename, e.salary,j.jname,j.description,d.dname,d.loc,sg.gradeFROM emp eINNER JOIN job j ON e.job_id = j.idINNER JOIN dept d ON e.dept_id = d.idINNER JOIN salarygrade sg ON e.salary BETWEEN sg.losalary AND sg.hisalary;
小结
1. 多表查询会出现笛卡尔积2. 消除笛卡尔积:2张表1个条件、3张表2个条件【条件=n-1】3. 步骤3.1 确定查询几张表?3.2 确定连接条件?3.3 确定显示字段?3.4 确定业务条件?
-- 4 查询经理的信息。显示员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
-- 4.1 确定查询几张表
SELECT * FROM emp eINNER JOIN job jINNER JOIN dept dINNER JOIN salarygrade sg;
-- 4.2 确定连接条件
SELECT * FROM emp eINNER JOIN job j ON e.job_id = j.idINNER JOIN dept d ON e.dept_id = d.idINNER JOIN salarygrade sg ON e.salary BETWEEN sg.losalary AND sg.hisalary;
-- 4.3 确定显示字段
SELECT e.id,e.ename, e.salary,j.jname,j.description,d.dname,d.loc,sg.gradeFROM emp eINNER JOIN job j ON e.job_id = j.idINNER JOIN dept d ON e.dept_id = d.idINNER JOIN salarygrade sg ON e.salary BETWEEN sg.losalary AND sg.hisalary;
-- 4.4 确定业务条件【经理】
SELECT e.id,e.ename, e.salary,j.jname,j.description,d.dname,d.loc,sg.gradeFROM emp eINNER JOIN job j ON e.job_id = j.idINNER JOIN dept d ON e.dept_id = d.idINNER JOIN salarygrade sg ON e.salary BETWEEN sg.losalary AND sg.hisalaryWHERE j.jname = '经理';
-- 5 查询出部门编号、部门名称、部门位置、部门人数
-- 5.1 先查询部门人数
SELECT dept_id,COUNT(*) AS total FROM emp GROUP BY dept_id;-- 5.2 查询结果作为虚拟表 连接 部门表
SELECT * FROM (SELECT dept_id,COUNT(*) AS total FROM emp GROUP BY dept_id) AS e INNER JOIN dept d ON e.dept_id = d.id;-- 内连接不关注书写的顺序,谁先谁后都可以
SELECT d.id,d.dname,d.loc,e.total FROM dept d INNER JOIN (SELECT dept_id,COUNT(*) AS total FROM emp GROUP BY dept_id) AS e ON d.id = e.dept_id;
三 事务安全 TCL
3.1 概述
如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。
准备数据
-- 创建数据表
CREATE TABLE account ( -- 账户表id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(32),money DOUBLE
);-- 添加数据
INSERT INTO account (NAME, money) VALUES ('片王', 1000), ('刚刚', 1000);
模拟转账
-- 转账
-- 片王扣钱 -100
UPDATE account SET money=money-100 WHERE id = 1;
-- 机器故障
-- 刚刚收钱 +100
UPDATE account SET money=money+100 WHERE id = 2;
3.2 操作
3.2.1 手动提交事务
1. 开启事务start transaction;2. 提交事务commit;3. 回滚事务rollback;
转账成功
-- 1.开启事务
start transaction;
-- 2.片王扣钱
UPDATE account SET money=money-100 WHERE id = 1;
-- 3.刚刚加钱
UPDATE account SET money=money+100 WHERE id = 2;
-- 4.提交事务
commit;
转账失败
-- 1.开启事务
start transaction;
-- 2.片王扣钱
UPDATE account SET money=money-100 WHERE id = 1;
-- 机器故障
-- 3.回滚事务
rollback;
3.2.2 自动提交事务
* MySQL数据库默认情况一条DML语句就是一个独立的事务。* 查看MySQL是否开启自动提交show variables like 'autocommit';* 临时关闭自动提交set autocommit=OFF;
测试还会不会自动提交
-- 片王扣钱
UPDATE account SET money=money-100 WHERE id = 1;
-- 手动提交事务
commit;
3.3 事务原理【理解】
注意:代码报错之后必须进行rollback
处理,如果没有执行rollback
会影响到同一个事务中下一次提交数据。
3.4 回滚点【了解】
* 功能当事务开启后,一部分sql执行成功,添加一个回滚点,后续操作报错了,直接到回滚点,保证之前的操作可以成功提交* 语法:1.添加保存点savepoint 保存点名称;2.回滚到保存点rollback to 保存点名称;
模拟,插入四条记录,在前二条插入成功后,设置保存点,后续操作报错了,直接到保存点,保证前二条记录可成功提交。
-- 1.开启事务
start transaction;
-- 2.插入 小路
insert into account values(null,'小路',1000);
-- 3.插入 PGONE
insert into account values(null,'pgone',1000);
-- 4.设置保存点
savepoint ok;
-- 5.插入 薛之谦
insert into account values(null,'谦谦',1000);
-- 机器故障
-- 6.回滚到保存点
rollback to ok;
-- 7.提交事务
commit;
3.5 事务特性【ACID】【面试题】
1. 原子性:A atomicity如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。2. 一致性:C consistency数据在事务的执行前后,保证它的完整一致3. 隔离性:I isolation多个事务之间,相互独立互不干扰4. 持久性:D durability事务一旦成功提交,同步到数据库磁盘文件,不可逆。
3.6 事务隔离性
* 功能多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题。1. 脏读:一个事务中,读取到另一个事务未提交的数据2. 不可重复读:一个事务中,二次读取的内容不一致(update)3. 幻读:一个事务中,二次读取的数量不一致(insert、delete)
MySQL数据库隔离级别
| 级别 | 名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 | | ---- | -------- | ---------------- | ---- | ---------- | ---- | ------------------ | | 1 | 读未提交 | read uncommitted | 是 | 是 | 是 | | | 2 | 读已提交 | read committed | 否 | 是 | 是 | Oracle和SQL Server | | 3 | 可重复读 | repeatable read | 否 | 否 | 是 | MySQL | | 4 | 串行化 | serializable | 否 | 否 | 否 | |
数据库性能:1>2>3>4
数据库安全:4>3>2>1
综合考量:2、3
演示不同隔离级别产生的问题
开启二个事务:A窗口 B窗口
1. 查看当前数据库隔离级别show variables like '%isolation%';
2. 临时修改隔离级别set session transaction isolation level 级别字符串;update account set money=900 where id = 1;
3. 演示3.1 脏读 设置隔离级别为 read uncommitted;set session transaction isolation level read uncommitted;3.2 解决脏读 设置隔离级别为 read committed;set session transaction isolation level read committed;3.3 解决不可重复读 设置隔离级别为 repeatable read;set session transaction isolation level repeatable read;4.3 串行化 设置隔离级别 serializable;set session transaction isolation level serializable;
四 三范式【了解】
4.1 概述
* 范式(Normal Form)设计数据库表的规则,好的数据库设计对数据的存储性能和后期的程序开发,都会产生重要的影响。建立科学的,规范的数据库就需要满足一些规则来优化数据的设计和存储 。* 分类目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)、第五范式(5NF,又称完美范式)。满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。一般说来,数据库只需满足第三范式(3NF)就行了。
4.2 三范式
看图
1. 第一范式(1NF)要求:每一列不能再拆分
2. 第二范式(2NF)要求:1.一张表只描述一件事情2.表中的每一个普通列都依赖于主键
3. 第三范式(3NF)要求:从表的外键必须使用主表的主键