DCL语句是控制数据库用户账号权限的sql语句。在管理员账号基础上对普通用户账号操作
我们现在默认使用的都是root用户,超级管理员,拥有全部的权限。但是,一个公司里面的数据库服务器上面可能同时运行着很多个项目的数据库。所以,我们应该可以根据不同的项目建立不同的用户,分配不同的权限来管理和维护数据库。
需要登录管理员账号再创建
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'
1、用户名
:将创建的用户名
2、主机名
:指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost,如果想让该用户可以从任意远程主机登陆,可以使用通配符%
3、密码
:该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器
创建的是普通用户账号
-- user1用户只能在localhost这个IP登录mysql服务器
CREATE USER 'user1'@'localhost' IDENTIFIED BY '123';
-- user2用户可以在任何电脑上登录mysql服务器
CREATE USER 'user2'@'%' IDENTIFIED BY '123';
用户创建之后,基本没什么权限!需要给用户授权
GRANT 权限1, 权限2... ON 数据库名.表名 TO '用户名'@'主机名';
1、GRANT
授权关键字
2、授予用户的权限,如SELECT
,INSERT
,UPDATE
等。如果要授予所的权限则使用ALL
3、数据库名.表名
:该用户可以操作哪个数据库的哪些表。如果要授予该用户对所有数据库和表的相应操作权限则可用*
表示:比如 *.*
4、'用户名'@'主机名'
: 给哪个用户授权
常用权限是对表或对表记录的增删改查:CREATE、ALTER、DROP、INSERT、UPDATE、DELETE、SELECT
不同的数据库有不同的权限,这里只写MySQL的权限。
用户详情的权限列表请参考MySQL官网说明:http://dev.mysql.com/doc/refman/5.7/en/privileges-provided.html
1、给user1用户分配对test这个数据库操作的权限(增删改查)
GRANT CREATE,ALTER,DROP,INSERT,UPDATE,DELETE,SELECT ON test.* TO 'user1'@'localhost';
2、给user2用户分配对所有数据库操作的权限
GRANT ALL ON *.* TO 'user2'@'%';
REVOKE 权限1, 权限2... ON 数据库.表名 FROM '用户名'@'主机名';
撤销user1用户对test操作的权限
REVOKE ALL ON test.* FROM 'user1'@'localhost';
SHOW GRANTS FOR '用户名'@'主机名';
查看user1用户的权限
SHOW GRANTS FOR 'user1'@'localhost';
DROP USER '用户名'@'主机名';
删除user2账号
DROP USER 'user2'@'%';
需要在未登陆MySQL管理员账号的情况下操作。
mysqladmin -uroot -p password 新密码 -- 新密码不需要加上引号
-- 注意:需要在未登陆MySQL的情况下操作。
mysqladmin -uroot -p password 123456
# 确定输入老密码
--Enter password:
需要在登陆MySQL管理员账号的情况下操作。
set password for '用户名'@'主机名' = password('新密码');
-- 注意:需要在登陆MySQL的情况下操作。
set password for 'user1'@'localhost' = password('新密码'); -- windows 系统
set password for 'user1'@'localhost' = '新密码'; -- max 系统
数据库可以通过:
source命令备份与还原
图形化界面备份与还原
mysql备份与还原的原理:
备份时,把数据库中的所有表和数据都会导出成SQL语句
还原时,把sql语句导入到数据库,再运行一遍
在服务器进行数据传输、数据存储和数据交换,就有可能产生数据故障。比如发生意外停机或存储介质损坏。这时,如果没有采取数据备份和数据恢复手段与措施,就会导致数据的丢失,造成的损失是无法弥补与估量的。
在DOS窗口输入语句操作
mysqldump -u用户名 -p密码 数据库 > 文件的路径
需要登录mysql账号,再还原
SOURCE 导入文件的路径;
mysqldump -uroot -proot day14 > C:\work\课改\MYSQL课改资料\Day02-MYSQL多表查询\code\bak.sql
还原的时候需要先登录MySQL,并选中对应的数据库
source C:\work\课改\MYSQL课改资料\Day02-MYSQL多表查询\code\bak.sql
在SQLyog中备份和还原
1、备份时,选中数据库,右键 ”备份/导出”,指定导出路径,保存成.sql文件即可。
2、还原时,数据库列表区域右键“执行SQL脚本”, 指定要执行的SQL文件,执行即可
范式是指:设计数据库表的规则(Normal Form)好的数据库设计对数据的存储性能和后期的程序开发,都会产生重要的影响。建立科学的,规范的数据库就需要满足一些规则来优化数据的设计和存储
理解:范式是一套用来设计数据表的规则,分第一,二,三,巴斯克科德,四,五范式。
目前关系数据库有六种范式:第一范式(1NF) 、第二范式(2NF) 、第三范式(3NF) 、巴斯-科德范式(BCNF) 、第四范式(4NF)和第五范式(5NF,又称完美范式) 。
满足最低要求的范式是第一范式(1NF) 。
在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF) ,其余范式以次类推。
一般说来,数据库只需满足第三范式(3NF)就行了。
第一范式:是最基本的要求,表中的每一列必须具有原子性(不可再分割)
即数据库表的每一列都是不可分割的原子数据项,而不能是集合、数组、记录等非原子数据项。即实体中的某个属性有多个值时,必须拆分为不同的属性。在符合第一范式(1NF)表中每个列的值只能是表的一个属性或一个属性的一部分。简而言之,第一范式每一列不可再拆分,称为原子性。第一范式:每一列不能再拆分
总结:如果不遵守第一范式,查询出数据还需要进一步处理(查询不方便)。遵守第一范式,需要什么字段的数据就查询什么数据(方便查询) 。
第二范式(2NF):要求在第一范式的基础上,数据库表中的每个实例或记录必须可以被唯一地区分,表中的每一个字段都依赖于主键。
即:第二范式在满足要第一范式的基础上,再满足如下两个条件:
1、每张表必须有主键
2、每张表只描述一件事件
第二范式(2NF)要求数据库表中的每个实例或记录必须可以被唯一地区分。选取一个能区分每个实体的属性或属性组,作为实体的唯一标识。例如在员工表中的身份证号码即可实现每个员工的区分,该身份证号码即为候选键,任何一个候选键都可以被选作主键。在找不到候选键时,可额外增加属性以实现区分。第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是在第一范式的基础上属性完全依赖于主键。
总结:如果不准守第二范式,数据冗余,相同数据无法区分。遵守第二范式减少数据冗余,通过主键区分相同数据。
第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个关系中不包含已在其它关系已包含的非主关键字信息。
在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF) 。简而言之,第三范式(3NF)要求一个关系中不包含已在其它关系已包含的非主关键字信息。
总结:如果不准守第三范式,可能会有相同数据无法区分,修改数据的时候多张表都需要修改(不方便修改)。遵守第三范式通过id可以区分相同数据,修改数据的时候只需要修改一张表(方便修改)。
数据库三大范式小结 | 说明 |
---|---|
1NF | 列必须具有原子性:不可再分割 |
2NF | 每张必须有主键且只描述一件事 |
3NF | 一张表不能引用另一张表的非主键字段 |
现实生活中,实体与实体之间肯定是有关系的,比如:老公和老婆,部门和员工,老师和学生等。那么我们在设计表的时候,就应该体现出表与表之间的这种关系!分成三种:
1、一对一
2、一对多
3、多对多
一对一(1:1)在实际的开发中应用不多,因为一对一可以创建成一张表。
一对一两种建表原则:
1、外键唯一:主表的主键和从表的外键(唯一) ,形成主外键关系。外键唯一UNIQUE(即从表的外键要添加唯一约束)
2、外键是主键:主表的主键和从表的主键,形成主外键关系(即从表的主键列也是外键,添加外键约束引用主表的主键)
理解:一对一,从表的行数据记录的外键引用了主表的主键,从表其他行数据记录就不能引用这个主表主键了(外键不能重复)。区分一对多
-- 创建数据库
create database day15;
use day15;
-- 一对一关系建表原则
-- 简历表:主表
create table jl(
id int primary key auto_increment,
content varchar(2000)
);
-- 学生表:从表
create table stu(
id int primary key auto_increment,
name varchar(20) not null,
jl_id int unique, -- 外键列
constraint foreign key(jl_id) references jl(id)
);
-- 一对一建表原则2
-- 先删从表
drop table stu;
-- 然后删除主表
drop table jl;
-- 简历表:主表
create table jl(
id int primary key auto_increment,
content varchar(2000)
);
-- 学生表:从表
create table stu(
id int primary key auto_increment,
name varchar(20) not null,
constraint foreign key(id) references jl(id)
);
一对一表关系建表原则小结 | 说明 |
---|---|
外键唯一 | 在从表中添加一列引用主表的主键,外键添加唯一约束 |
主键是外键 | 从表的主键和主表的主键形成主外键关系 |
所以在开发中一对一建表很少用,两张表可以合成一张表,因为主键都是一样的(第二范式使用看情况,并不是死规定一张表只描述一件事)
一对多(1:n) 建表,例如:班级和学生,部门和员工,客户和订单,分类和商品。
一对多建表原则:
在从表(多方)创建一个字段,字段作为外键指向主表(一方)的主键
理解:一对多,从表的行数据记录的外键引用了主表的主键,从表其他行数据记录就还能引用这个主表主键(外键可以重复)。
-- 一对多建表演示
-- 创建部门表:主表
create table dept(
id int primary key auto_increment,
dept_name varchar(20) not null,
dept_loc varchar(200) not null
);
-- 创建从表 employee 并添加外键约束
create table employee(
id int primary key auto_increment,
name varchar(20) not null,
age int,
dept_id int,
constraint foreign key (dept_id) references dept(id)
);
一对多建表原则小结 | 说明 |
---|---|
在从表(多方)中添加一列作为外键引用主表(一方)的主键 |
多对多(m:n),例如:老师和学生,学生和课程,用户和角色
多对多关系建表原则:
需要创建第三张表,中间表中至少两个字段,这两个字段分别作为外键指向各自一方的主键。
理解:多对多是在一对多的基础上增加第三张表,多方为中间表,一方为主表和从表。主表和中间表,从表和中间表,都是一对多的关系,主表/从表是一,中间表是多。
中间表两个字段添加外键约束,各引用主表/从表的主键。这两外键约束字段组合确定中间表记录的唯一,叫联合主键
-- 学生表
create table student(
id int primary key auto_increment,
name varchar(20) not null
);
-- 课程表
create table course(
id int primary key auto_increment,
name varchar(20) not null
);
-- 中间表
create table student_course(
sid int,
cid int,
constraint foreign key(sid) references student(id), -- 外键约束
constraint foreign key(cid) references course(id), -- 外键约束
primary key(sid,cid) -- 联合主键
);
多对多建表原则小结 | 说明 |
---|---|
需要创建中间表,中间表至少需要两个字段,这两个字段分别作为外键引用一方的主键 这两个外键要一起做联合主键:共同确定中间表记录的唯一性 |
需求:一个旅游线路分类中有多个旅游线路
创建旅游线路分类表
CREATE TABLE tab_category (
cid INT PRIMARY KEY AUTO_INCREMENT, -- 旅游线路分类主键
cname VARCHAR(100) NOT NULL UNIQUE -- 旅游线路分类名称
);
添加旅游线路分类数据
INSERT INTO tab_category (cname) VALUES ('周边游'), ('出境游'), ('国内游'), ('港澳游');
创建旅游线路表
CREATE TABLE tab_route (
rid INT PRIMARY KEY AUTO_INCREMENT, -- 旅游线路主键
rname VARCHAR(100) NOT NULL UNIQUE, -- 旅游线路名称
price DOUBLE NOT NULL, -- 价格
routeIntroduce VARCHAR(200), -- 线路介绍
rflag CHAR(1) NOT NULL, -- 是否上架
rdate VARCHAR(19) NOT NULL, -- 上架时间
isThemeTour CHAR(1) NOT NULL, -- 是否主题旅游
acount INT DEFAULT 0, -- 收藏数量
cid INT NOT NULL, -- 所属分类
rimage VARCHAR(200) NOT NULL, -- 缩略图地址
CONSTRAINT ro_cid_ref_cate_id FOREIGN KEY(cid) REFERENCES tab_category(cid) -- 线路表添加外键约束,引用分类表主键
);
添加旅游线路数据
INSERT INTO tab_route VALUES
(NULL, '【厦门+鼓浪屿+南普陀寺+曾厝垵高铁3天惠贵团】尝味友鸭面线住1晚鼓浪屿', 1499, '2018-01-27', 1),
(NULL, '【浪漫桂林阳朔西街高铁3天纯玩高级团】城徽象鼻山兴坪漓江西山公园', 699, '2018-02-22', 3),
(NULL, '【爆款¥1699秒杀】泰国曼谷芭堤雅金沙岛杜拉拉水上市场双飞六天【含送签费泰风情广州往返特价团】', 1699, '2018-01-27', 2),
(NULL, '【经典•狮航¥2399秒杀】巴厘岛双飞五天抵玩【广州往返特价团】', 2399, '2017-12-23', 2),
(NULL, '香港迪士尼乐园自由行2天【永东跨境巴士广东至迪士尼去程交通+迪士尼一日门票+香港如心海景酒店暨会议中心标准房1晚住宿】', 799, '2018-04-10', 4);
需求:一个用户收藏多个线路,一个线路被多个用户收藏
创建用户表
CREATE TABLE tab_user (
uid INT PRIMARY KEY AUTO_INCREMENT, -- 用户id
username VARCHAR(100) NOT NULL UNIQUE, -- 用户名
PASSWORD VARCHAR(30) NOT NULL, -- 密码
NAME VARCHAR(100), -- 真实姓名
birthday DATE, -- 生日
sex CHAR(1), -- 性别
telephone VARCHAR(11), -- 手机号
email VARCHAR(100), -- 邮箱
STATUS CHAR(1) NOT NULL, -- 是否激活状态
CODE VARCHAR(32) NOT NULL UNIQUE -- 激活码
);
添加用户数据
INSERT INTO tab_user VALUES
(NULL, 'cz110', 123456, '老王', '1977-07-07', '男', '13888888888', '[email protected]'),
(NULL, 'cz119', 654321, '小王', '1999-09-09', '男', '13999999999', '[email protected]');
创建收藏表
CREATE TABLE tab_favorite (
fid INT PRIMARY KEY AUTO_INCREMENT, -- 收藏主键
rid INT NOT NULL, -- 旅游线路id
DATE DATE NOT NULL, -- 收藏时间
uid INT NOT NULL, -- 用户id
CONSTRAINT FOREIGN KEY(uid) REFERENCES tab_user(uid), -- 引用用户表的主键
CONSTRAINT FOREIGN KEY(rid) REFERENCES tab_route(rid) -- 引用旅游路线表的主键
);
增加收藏表数据(收藏表就是中间表)
INSERT INTO tab_favorite VALUES
(1, '2018-01-01', 1), -- 老王选择厦门
(2, '2018-01-01', 1), -- 老王选择桂林
(3, '2018-01-01', 1), -- 老王选择泰国
(2, '2018-01-01', 2), -- 小王选择桂林
(3, '2018-01-01', 2), -- 小王选择泰国
(5, '2018-01-01', 2); -- 小王选择迪士尼
同时查询多张表获取到需要的数据。比如:我们想查询到开发部有多少人,需要将部门表和员工表同时进行查询
多表查询的使用场景:当需要查询的数据存储在多张表时
-- 创建部门表(部门编号,部门名称)
create table dept(
id int primary key auto_increment,
name varchar(20) not null unique
);
-- 插入三个部门:开发部,市场部,财务部
insert into dept(name) values('开发部'),('市场部'),('财务部');
-- 创建员工表(员工编号,员工姓名,性别,工资,入职日期,部门编号)
create table emp(
id int primary key auto_increment,
name varchar(20) not null,
gender char(1),
salary double,
join_date date,
dept_id int,
constraint foreign key(dept_id) references dept(id)
);
-- 插入员工数据
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);
select * from dept;
select * from emp;
多表查询时左表的每条数据和右表的每条数据组合,这种效果称为笛卡尔积
SELECT * FROM dept, emp;
以上数据其实是左表的每条数据和右表的每条数据组合。左表有3条,右表有5条,最终组合后3*5=15条数据。
左表的每条数据和右表的每条数据组合,这种效果称为笛卡尔乘积
确定过滤条件,员工表.dept_id = 部门表.id 的数据才是有效的
select * from emp , dept where dept_id = dept.id;
交叉连接查询:查询结果就是笛卡尔积现象。
内连接查询
1、隐式内连接
2、显式内连接
外连接查询
1、左外连接查询
2、右外连接查询
Mysql—— 内连接、左连接、右连接以及全连接查询
https://blog.csdn.net/zjt980452483/article/details/82945663
用左边表的记录去匹配右边表的记录,如果符合条件的则显示
隐式内连接:看不到 JOIN 关键字,条件使用 WHERE 指定
SELECT 字段名 FROM 左表, 右表 WHERE 条件;
显示内连接:条件使用 INNER JOIN … ON 指定,可以省略 INNER 关键字
SELECT 字段名 FROM 左表 INNER JOIN 右表 ON 条件;
# 查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称。
-- 隐式内连接:在笛卡尔积基础上使用where加过滤条件:主表.主键=从表.外键
select e.id 员工编号, e.name 姓名, e.gender 性别, e.salary 工资, d.name 部门名称 -- 要查询的字段
from emp e, dept d -- 要查询的表
where e.`dept_id` = d.`id` order by e.`id` -- 查询条件
-- 显示内连接查询:inner join 表名 on 连接条件
select e.id 员工编号, e.name 姓名, e.gender 性别, e.salary 工资, d.name 部门名称 -- 要查询的字段
from emp e -- 要查询的表
inner join dept d -- 要连接的表
on e.`dept_id` = d.`id` -- 连接条件
order by e.id;
需求:查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部⻔门名称,我们发现需要联合2张表同时才能查询出需要的数据,我们使用内连接
1、确定查询哪些表
SELECT * FROM dept INNER JOIN emp;
2、确定表连接条件,员工表.dept_id = 部门表.id 的数据才是有效的
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
3、确定表连接条件,我们查询的是唐僧的信息,员工表.name=‘唐僧’
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id` AND emp.`NAME`='唐
僧';
4、确定查询字段,查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称
SELECT emp.`id`, emp.`NAME`, emp.`gender`, emp.`salary`, dept.`NAME` FROM dept
INNER JOIN emp ON emp.`dept_id`=dept.`id` AND emp.`NAME`='唐僧';
5、我们发现写表名有点长,可以给表取别名,显示的字段名也使用别名
SELECT e.`id` 员工编号, e.`NAME` 员工姓名, e.`gender` 性别, e.`salary` 工资,
d.`NAME` 部门名称 FROM dept d INNER JOIN emp e ON e.`dept_id`=d.`id` AND
e.`NAME`='唐僧';
总结内连接查询步骤:
内连接查询分类 | 语法 |
---|---|
隐式内连接 | select 字段名 from 左表,右表 where 条件; |
显示内连接 | select 字段名 from 左表 inner join 右表 on 条件; |
左外连接是:用左边表的记录去匹配右边表的记录,如果符合条件的则显示;否则,显示NULL(内连接查询null的不显示)。可以理解为:在内连接的基础上保证左表的数据全部显示。
左连接:使用LEFT OUTER JOIN … ON关键字, OUTER可以省略
SELECT 字段名 FROM 左表 LEFT OUTER JOIN 右表 ON 条件;
内连接与左外连接的对比,结果参考示例2图
# 将左连接查询员工信息和部门信息(没有员工的部门也要查询出来)
-- 左外连接查询:left outer join 表名 on 连接条件
-- 特点:能够保证左表的记录全部查询出来
# 在部门表中增加一个销售部
insert into dept(name) values('销售部');
# 使用内连接查询员工信息和部门信息
select d.*,e.* -- 要查询的字段
from emp e -- 要查询的表
inner join dept d -- 要连接的表
on e.`dept_id` = d.`id` -- 连接条件
order by e.id;
# 将左连接查询员工信息和部门信息(没有员工的部门也要查询出来)
select d.*,e.* -- 要查询的字段
from dept d -- 要查询的表
left join emp e -- 要连接的表
on e.`dept_id` = d.`id` -- 连接条件
数据准备
# 在部门表中增加一个销售部
insert into dept(name) values('销售部');
# 使用内连接查询员工信息和部门信息
select d.*,e.* -- 要查询的字段
from emp e -- 要查询的表
inner join dept d -- 要连接的表
on e.`dept_id` = d.`id` -- 连接条件
order by e.id;
使用左外连接查询
SELECT * FROM dept LEFT OUTER JOIN emp ON emp.`dept_id`=dept.`id`;
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
左外连接查询小结 |
---|
语法:select 字段名 from 左表 left outer join 右表 on 条件; – outer可以省略 |
特点:能够保证左表记录全部查询出来 |
右外连接是:用右边表的记录去匹配左边表的记录,如果符合条件的则显示;否则,显示NULL。可以理解为:在内连接的基础上保证右表的数据全部显示
右连接:使用RIGHT OUTER JOIN … ON关键字, OUTER可省略
SELECT 字段名 FROM 左表 RIGHT OUTER JOIN 右表 ON 条件;
数据准备:在员工表中增加一个员工
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('沙僧','男',6666,'2013-02-24',NULL);
使用右外连接查询
SELECT * FROM dept RIGHT OUTER JOIN emp ON emp.dept_id=dept.id;
SELECT * FROM dept INNER JOIN emp ON emp.dept_id=dept.id;
全连接关键字FULL OUTER JOIN,全连接表示把A表和B表的记录都显示出来,不符合条件的用NULL表示。
SELECT 字段名 FROM 左表 FULL OUTER JOIN 右表 ON 条件;
MySQL不支持全连接查询,可以用UNION关键字解决
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
select * from A left join B on A.id = B.id (where 条件)
union
select *from A right join B on A.id = B.id (where条件);
-- 全连接查询:full join 表名 on 条件 MySQL不支持
select d.*,e.* -- 要查询的字段
from dept d -- 要查询的表
full join emp e -- 要连接的表
on e.`dept_id` = d.`id` -- 连接条件
使用union实现
SELECT * FROM dept LEFT OUTER JOIN emp ON emp.`dept_id`=dept.`id`
union
SELECT * FROM dept RIGHT OUTER JOIN emp ON emp.dept_id=dept.id;
mysql不支持full join的另一种解决办法 和根据多个表中的相同分组来连接查询:
https://www.cnblogs.com/liuyifan/p/4985512.html
子查询就是:一条SELECT语句果作为另一条SELECT语法一部分(作为查询条件,查询结果,表)。
子查询语句需要放在()中
子查询示例
# 根据最高工资到员工表查询到对应的员工信息
SELECT * FROM employee WHERE salary=(SELECT MAX(salary) FROM employee);
salary=() 括号里面的就是子查询,括号外的就是父查询
子查询结果的有三种情况:
1、子查询的结果是一个值
子查询结果只要是单列,肯定在WHERE后面作为条件
SELECT 查询字段 FROM 表 WHERE 字段=(子查询);
2、子查询结果是单列多行
子查询结果只要是单列,肯定在WHERE后面作为条件。子查询结果是单例多行,结果集类似于一个数组,父查询使用IN运算符
SELECT 查询字段 FROM 表 WHERE 字段 IN (子查询)
3、子查询的结果是多行多列(即一张表)
子查询结果只要是多列,肯定在FROM后面作为表
SELECT 查询字段 FROM (子查询) 表别名 WHERE 条件;
子查询作为表需要取别名,否则这张表没有名称无法访问表中的字段
说明:子查询结果只要是单列,肯定在WHERE后面作为条件。子查询结果只要是多列,肯定在FROM后面作为表
单行单列子查询概念
子查询结果是单行单列的值,父查询可以用比较运算符:< > =
-- 单行单列子查询:子查询结果是单行单列的值
# 查询工资最高的员工是谁
-- 先查询最高工资是多少
select max(salary) from emp;
-- 然后再查询工资等于最高工资的员工信息
select * from emp where salary = (select max(salary) from emp);
# 查询工资小于平均工资的员工有哪些
-- 先查询平均工资是多少
select avg(salary) from emp;
-- 然后再查询工资小于平均工资的员工信息
select * from emp where salary < (select avg(salary) from emp);
# 查询工资大于"蜘蛛精"的员工
-- 先查询蜘蛛精工资
select salary from emp where name = '蜘蛛精';
-- 然后查询工资大于蜘蛛精工资的员工信息
select * from emp where salary > (select salary from emp where name = '蜘蛛精');
多行单列子查询概述
子查询结果是多行单列的值,父查询使用in运算符
-- 多行单列子查询
# 查询工资大于5000的员工,来自于哪些部门的名字
-- 先查询工资大于5000的员工的部门id:查询员工表
select dept_id from emp where salary > 5000;
-- 然后根据部门id查询部门名称:查询部门表
select * from dept where id in (select dept_id from emp where salary > 5000);
# 查询开发部与财务部所有的员工信息
-- 先查询开发部与财务部的部门id:查询部门表
select id from dept where name in ('开发部','财务部');
-- 然后查询开发部和财务部的员工信息:查询员工表
select * from emp where dept_id in (select id from dept where name in ('开发部','财务部'));
多行多列子查询概述
子查询结果是多行多列的值:一张虚拟表,可以和其他表进行表连接查询
-- 多行多列子查询:子查询结果是多行多列的值:一张虚拟表,可以和其他表进行表连接查询
# 查询2011年以后入职的员工信息和部门信息。
-- 先查询2011年以后入职的员工信息:查询员工表
select * from emp where join_date > '2010-12-31';
-- 查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门id等于的dept id
select e.*,d.* -- 要查询的字段
from dept d -- 要查询的表
right join (select * from emp where join_date > '2010-12-31') e -- 要连接的表
on e.dept_id = d.id-- 连接条件
子查询结果只要是单列,肯定在WHERE后面作为条件
SELECT 查询字段 FROM 表 WHERE 字段=(子查询);
子查询结果只要是多列,肯定在FROM后作为表
SELECT 查询字段 FROM (子查询)表别名 WHERE 条件;
目标:通过该案例深入理解多表查询的步骤和规律。
-- 部门表
CREATE TABLE dept (
id INT PRIMARY KEY PRIMARY KEY, -- 部门id
dname 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, -- 员工id
ename VARCHAR(50), -- 员工姓名
job_id INT, -- 职务id
mgr INT , -- 上级领导
joindate DATE, -- 入职日期
salary DECIMAL(7,2), -- decimal(5,2) 工资
bonus DECIMAL(7,2), -- 奖金
dept_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);
# 查询所有员工信息。显示员工编号,员工姓名,工资,职务名称,职务描述
select e.id 员工编号, e.`ename` 员工姓名,
e.`salary` 工资, j.`jname` 职务名称, j.`description` 职务描述 -- 要查询的字段
from emp e -- 要查询的表
inner join job j -- 要连接的表
on e.`job_id` = j.`id`-- 连接条件
# 查询所有员工信息。显示员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
select e.id 员工编号, e.`ename` 员工姓名,
e.`salary` 工资, j.`jname` 职务名称, j.`description` 职务描述,
d.dname 部门名称, d.loc 部门位置 -- 要查询的字段
from emp e -- 要查询的表
inner join job j -- 要连接的表
inner join dept d -- 要连接的表
on e.job_id = j.`id` -- 连接条件
and e.`dept_id` = d.id
order by e.id;
# 查询所有员工信息。显示员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
select e.id 员工编号, e.`ename` 员工姓名,
e.`salary` 工资, j.`jname` 职务名称, j.`description` 职务描述,
d.dname 部门名称, d.loc 部门位置, s.`grade` 工资等级 -- 要查询的字段
from emp e -- 要查询的表
inner join job j -- 要连接的表
inner join dept d -- 要连接的表
inner join salarygrade s -- 要连接的表
on e.job_id = j.`id` -- 连接条件
and e.`dept_id` = d.id
and e.`salary` between s.`losalary` and s.`hisalary`; -- [70,90]
# 查询经理的信息。显示员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
select e.id 员工编号, e.`ename` 员工姓名,
e.`salary` 工资, j.`jname` 职务名称, j.`description` 职务描述,
d.dname 部门名称, d.loc 部门位置, s.`grade` 工资等级 -- 要查询的字段
from emp e -- 要查询的表
inner join job j -- 要连接的表
inner join dept d -- 要连接的表
inner join salarygrade s -- 要连接的表
on e.job_id = j.`id` -- 连接条件
and e.`dept_id` = d.id
and e.`salary` between s.`losalary` and s.`hisalary`
and j.`jname` = '经理';
# 查询出部门编号、部门名称、部门位置、部门人数
select d.id 部门编号, d.`dname` 部门名称, d.loc 部门位置, ifnull(e.total,'没有人') 部门人数 -- 要查询的字段
from (select dept_id, count(*) total from emp group by dept_id) e -- 要查询的表
right join dept d -- 要连接 的表
on d.id = e.dept_id; -- 连接条件
-- ifnull(字段名,默认值) 如果字段的值为null,则使用默认值代替
-- ifnull(e.total,0)
事务(Transaction),一般是指要做的或所做的事情。由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
在实际的业务开发中,有些业务操作要多次访问数据库。一个业务要发送多条SQL语句给数据库执行。需要将多次访问数据库的操作视为一个整体来执行,要么所有的SQL语句全部执行成功。如果其中有一条SQL语句失败,就进行事务的回滚,所有的SQL语句全部执行失败。
例如:jack给rose转账,jack账号减钱,rose账号加钱
-- 创建账户表
create table account(
id int primary key auto_increment,
username varchar(20) not null,
balance double
);
-- 添加账户数据
insert into account(username,balance) values('jack',1000),('rose',1000);
select * from account;
模拟jack给rose转500元钱,一个转账的业务操作最少要执行下面的2条语句:
1、jack账号-500
2、rose账号+500
-- 1. jack账号-500
update account set balance = balance - 500 where id = 1;
-- 2. rose账号+500
update account set balance = balance + 500 where id = 2;
select * from account;
假设当jack账号上-500元,服务器崩溃了。rose的账号并没有+500元,数据就出现问题了。我们需要保证其中一条SQL语句出现问题,整个转账就算失败。只有两条SQL都成功了转账才算成功。这个时候就需要用到事务
事务应用
将一组业务操作中的多条SQL语句当成一个整体对待,这多条SQL语句要么全部执行成功,要么全部执行失败,如果有一条sql语句执行失败,则回滚已经成功执行的SQL语句
MYSQL中可以有两种方式进行事务的操作:
手动提交事务使用步骤:
第1种情况:开启事务>执行SQL语句->成功>提交事务
第2种情况:开启事务>执行SQL语句>失败->回滚事务
MySQL的每一条DML(增删改)语句都是一个单独的事务,每条语句都会自动开启一个事务,执行完毕自动提交事务,MySQL默认开始自动提交事务
即相当于每条语句前面有一个start transaction,后面有一个commit
目标:能够使用SQL语句对事务进行管理
模拟张三给李四转500元钱(成功)
未转账前数据库数据如下:
1、使用DOS控制台进入MySQL
2、执行以下SQL语句: 1.开启事务
, 2.张三账号-500
, 3.李四账号+500
START TRANSACTION;
UPDATE account SET balance = balance - 500 WHERE id=1;
UPDATE account SET balance = balance + 500 WHERE id=2;
3、使用SQLYog查看数据库:发现数据并没有改变
4、在控制台执行commit
提交任务
5、使用SQLYog查看数据库:发现数据改变
目标:能够禁止事务自动提交
UPDATE account SET balance = balance - 500 WHERE id=1;
3、使用SQLYog查看数据库:发现数据已经改变
4、通过修改MySQL全局变量"autocommit",取消自动提交事务
使用SQL语句:查看MySQL是否开启自动提交事务
show variables like '%commit%';
5、取消自动提交事务
设置自动提交的参数为OFF,执行SQL语句:
set autocommit = 0;
UPDATE account SET balance = balance - 500 WHERE id=1;
7、使用SQLYog查看数据库,发现数据并没有改变
8、在控制台执行commit
提交任务
9、使用SQLYog查看数据库,发现数据改变
事务开启之后, 所有的操作都会临时保存到事务日志, 事务日志只有在得到commit
命令才会同步到数据表中,其他任何情况都会清空事务日志(rollback,断开连接
)
1、当执行start transaction开启事务
2、接下来执行的所有SQL语句的结果都会先保存到临时的日志文件中:没有同步到数据库中
3、当执行commit语句时,才会将日志文件中记录的结果同步到数据库中并清空日志文件中的内容
4、当执行rollback语句时,仅仅将日志文件的内容清空并没有同步到数据库中
在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点。
设置回滚点语法:
savepoint 回滚点名字;
回到回滚点语法:
rollback to 回滚点名字;
UPDATE account SET balance = balance - 10 WHERE id=1;
UPDATE account SET balance = balance - 10 WHERE id=1;
UPDATE account SET balance = balance - 10 WHERE id=1;
4、设置回滚点:savepoint abc;
5、让张三账号减4次钱
UPDATE account SET balance = balance - 10 WHERE id=1;
UPDATE account SET balance = balance - 10 WHERE id=1;
UPDATE account SET balance = balance - 10 WHERE id=1;
UPDATE account SET balance = balance - 10 WHERE id=1;
6、回到回滚点:rollback to abc;
7、分析过程
回滚点的作用:可以让我们在失败的时候回到指定位置(回滚点),而不是回到事务开启的时候。
设置回滚点的语法:
savepoint 回滚点名称;
回到回滚点的语法:
rollback to 回滚点名称;
事务四大特性 | 说明 |
---|---|
A:atomic 原子性 | 事务中的多条SQL语句是一个整体,不可再分割 要么全部执行成功,要么全部执行失败 |
C:Consistency 一致性 | 事务前后的数据要保持一致 比如转账前和转账后的总金额要一致 |
I:isolation 隔离性 | 多个事务之间的操作不能相互影响 |
D:durablity 持久性 | 事务一旦提交对数据库数据影响是永久的,不可逆的 |
事务在操作时的理想状态:多个事务之间互不影响,如果隔离级别设置不当就可能引发并发访问问题。
B窗口事务读取到A窗口事务修改后未提交的数据
脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入500块,然后打电话给李四说钱已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四的再查看钱没了。
B窗口事务查询2次,两次的数据不一样。第2次查询读取到了A窗口事务修改后的数据。
不可重复读:一个事务前后读取数据不一致,一般是一个事务在查询,另一个事务在执行update操作引起的
两次查询输出的结果不同,到底哪次是对的?不知道以哪次为准。很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客户,结果在一个事务中针对不同的输出目的进行的两次查询不一致,导致短信和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。
不可重复读 与 脏读 的区别
1、本事务都是读取到另外一个事务修改的数据
2、脏读是本事务读取1次,读取到另一个事务修改后未提交的数据(有可能回滚)
3、不可重复读本事务读取2次,第1次读取的正确,第2次读取的是另一个事务修改后已提交的数据(不回滚),本事务未修改数据但2次数据不一致。
A窗口事务查询2次,两次数据的数量不一样。第2次查询把到了B窗口事务增加/删除后的数据数量。
幻读:一个事务前后读取的记录数不一致,一般是一个事务在统计数量,另一个事务在执行insert或delete操作
幻读 和 不可重复读 的区别
1、两个都是查询2次
2、不可重复读,第2次查询的是另一个事务修改的数据(2次查询,数据内容不同)
3、幻读,第2次查询的是另一个事务增加或删除后的数据(2次查询,数据数量不同)
MySQL数据库有四种隔离级别:
上面的级别最低,下面的级别最高。
"是"表示会出现这种问题, "否"表示不会出现这种问题。
在A窗口设置全局的隔离级别为read committed
set global transaction isolation level read committed;
A窗口事务修改数据未提交时,此是B窗口事务再查询,查询出的是A窗口事务未修改前的数据。A窗口提交后,B窗口才会查询出修改后的数据。(A窗口设置全局隔离后,B窗口需关闭重开才会在B窗口生效)
结论: read committed的方式可以避免脏读的发生
将A窗口全局的隔离级别进行提升为: repeatable read
set global transaction isolation level repeatable read;
更改A窗口事务隔离级别后,在B窗口事务第1次第2次查询之间,A窗口事务再修改数据并提交,B窗口事务第2次查询结果和第一次相同。
结论:同一个事务中为了保证多次查询数据一致,必须使用repeatable read隔离级别
将A窗口全局的隔离级别进行提升为: repeatable read
set global transaction isolation level serializable; -- 设置隔离级别为最高
A窗口开启事务,查询数据数量(统计数量),未提交查询事务。B窗口开启事务,执行插入或删除数据sql语句,会陷入阻塞。直到A窗口提交了查询事务,B窗口才会继续执行插入或删除数据sql语句。A窗口再查询,会查询到最新的数据数量
结论:使用serializable隔离级别,一个事务没有执行完,其他事务的SQL执行不了,可以挡住幻读。(可理解为同步锁)