说明:本内容整理自B站黑马程序员Java web课程视频及文档>>B站黑马程序员Java web课程视频
1. 表连接查询
1.1 多表查询
数据准备
# 创建部门表
create table dept(
id int primary key auto_increment,
name varchar(20)
);
insert into dept (name) values ('开发部'),('市场部'),('财务部');
select * from dept;
/*+----+--------+
| id | name |
+----+--------+
| 1 | 开发部 |
| 2 | 市场部 |
| 3 | 财务部 |
+----+--------+*/
# 创建员工表
create table emp(
id int primary key auto_increment,
name varchar(10),
gender char(1), # 性别
salary double, # 工资
join_date date, # 入职日期
dept_id int,
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 emp;
/*+----+--------+--------+--------+------------+---------+
| id | name | gender | salary | join_date | dept_id |
+----+--------+--------+--------+------------+---------+
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 |
+----+--------+--------+--------+------------+---------+*/
多表查询的作用:
比如:我们想查询孙悟空的名字和他所在的部门的名字,则需要使用多表查询。
如果一条 SQL 语句查询多张表,因为查询结果在多张不同的表中。每张表取 1 列或多列
多表查询的分类:
- 多表查询
- 内连接
- 显示内连接
- 隐式内连接
- 外连接
- 左外连接
- 右外连接
- 内连接
1.2 笛卡尔积现象
什么是笛卡尔积:
#需求:查询所有的员工和所有的部门
select * from emp,dept;
/*+----+--------+--------+--------+------------+---------+----+--------+
| id | name | gender | salary | join_date | dept_id | id | name |
+----+--------+--------+--------+------------+---------+----+--------+
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 3 | 财务部 |
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 2 | 市场部 |
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 1 | 开发部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 3 | 财务部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 2 | 市场部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 1 | 开发部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 3 | 财务部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 2 | 市场部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 1 | 开发部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 3 | 财务部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 2 | 市场部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 1 | 开发部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 3 | 财务部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 2 | 市场部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 1 | 开发部 |
+----+--------+--------+--------+------------+---------+----+--------+*/
如何清除笛卡尔积现象的影响
我们发现不是所有的数据组合都是有用的,只有员工表.dept_id = 部门表.id 的数据才是有用的。所以需要通过条件过滤掉没用的数据
#设置过滤条件 Column 'id' in where clause is ambiguous
select * from emp,dept where id=5;
select * from emp,dept where emp.`dept_id` = dept.`id`;
/*+----+--------+--------+--------+------------+---------+----+--------+
| id | name | gender | salary | join_date | dept_id | id | name |
+----+--------+--------+--------+------------+---------+----+--------+
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 1 | 开发部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 1 | 开发部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 2 | 市场部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 2 | 市场部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 3 | 财务部 |
+----+--------+--------+--------+------------+---------+----+--------+*/
#查询员工和部门的名字
select emp.`name`, dept.`name` from emp,dept where emp.`dept_id` = dept.`id`;
/*+--------+--------+
| name | name |
+--------+--------+
| 孙悟空 | 开发部 |
| 蜘蛛精 | 开发部 |
| 猪八戒 | 市场部 |
| 唐僧 | 市场部 |
| 白骨精 | 财务部 |
+--------+--------+*/
1.3 内连接
用左边表的记录去匹配右边表的记录,如果符合条件的则显示。如:从表.外键=主表.主键
1.3.1 隐式内连接
隐式内连接:看不到 JOIN 关键字,条件使用 WHERE 指定
#语法
SELECT 字段名 FROM 左表, 右表 WHERE 条件
#例子
select * from emp,dept where emp.`dept_id` = dept.`id`;
1.3.2 显式内连接
显示内连接:使用 INNER JOIN … ON 语句, 可以省略 INNER
SELECT 字段名 FROM 左表 [INNER] JOIN 右表 ON 条件
#1.确定查询哪些表
select * from
emp inner join dept;
/*+----+--------+--------+--------+------------+---------+----+--------+
| id | name | gender | salary | join_date | dept_id | id | name |
+----+--------+--------+--------+------------+---------+----+--------+
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 3 | 财务部 |
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 2 | 市场部 |
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 1 | 开发部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 3 | 财务部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 2 | 市场部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 1 | 开发部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 3 | 财务部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 2 | 市场部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 1 | 开发部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 3 | 财务部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 2 | 市场部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 1 | 开发部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 3 | 财务部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 2 | 市场部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 1 | 开发部 |
+----+--------+--------+--------+------------+---------+----+--------+*/
#2.确定表连接条件,员工表.dept_id = 部门表.id 的数据才是有效的
select * from
emp e inner join dept d
on e.`dept_id` = d.`id`;
/*+----+--------+--------+--------+------------+---------+----+--------+
| id | name | gender | salary | join_date | dept_id | id | name |
+----+--------+--------+--------+------------+---------+----+--------+
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 | 1 | 开发部 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 | 1 | 开发部 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 | 2 | 市场部 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 2 | 市场部 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 | 3 | 财务部 |
+----+--------+--------+--------+------------+---------+----+--------+*/
# 3.确定查询条件,我们查询的是唐僧的信息,员工表.name='唐僧'
select * from
emp e inner join dept d
on e.`dept_id` = d.`id`
where e.`name`='唐僧';
/*+----+------+--------+--------+------------+---------+----+--------+
| id | name | gender | salary | join_date | dept_id | id | name |
+----+------+--------+--------+------------+---------+----+--------+
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 | 2 | 市场部 |
+----+------+--------+--------+------------+---------+----+--------+*/
#4.确定查询字段,查询唐僧的信息,显示员工 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`='唐僧';
/*+----+------+--------+--------+--------+
| id | name | gender | salary | name |
+----+------+--------+--------+--------+
| 3 | 唐僧 | 男 | 9000 | 市场部 |
+----+------+--------+--------+--------+*/
#5.写表名有点长,可以给表取别名,显示的字段名也使用别名
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`='唐僧';
/*+------+------+------+------+----------+
| 编号 | 姓名 | 性别 | 工资 | 部门名字 |
+------+------+------+------+----------+
| 3 | 唐僧 | 男 | 9000 | 市场部 |
+------+------+------+------+----------+*/
1.3.3 内连接查询步骤
总结内连接查询步骤:
- 确定查询哪些表
- 确定表连接的条件
- 确定查询的条件
- 确定查询的字段
1.4 左外连接
左外连接:使用 LEFT OUTER JOIN … ON,OUTER 可以省略
SELECT 字段名
FROM 左表 LEFT [OUTER] JOIN 右表
ON 条件
用左边表的记录去匹配右边表的记录,如果符合条件的则显示;否则,显示 NULL
可以理解为:在内连接的基础上保证左表的数据全部显示(左表是部门,右表员工)
#在部门表中增加一个销售部
insert into dept (name) values ('销售部');
select * from dept;
/*+----+--------+
| id | name |
+----+--------+
| 1 | 开发部 |
| 2 | 市场部 |
| 3 | 财务部 |
| 4 | 销售部 |
+----+--------+*/
#使用内连接查询
#select * from
dept d inner join emp e
on d.`id` = e.`dept_id`;
/*+----+--------+----+--------+--------+--------+------------+---------+
| id | name | id | name | gender | salary | join_date | dept_id |
+----+--------+----+--------+--------+--------+------------+---------+
| 1 | 开发部 | 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 |
| 1 | 开发部 | 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 |
| 2 | 市场部 | 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 |
| 2 | 市场部 | 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 |
| 3 | 财务部 | 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 |
+----+--------+----+--------+--------+--------+------------+---------+*/
#使用左外连接查询
select * from
dept d left join emp e
on d.`id` = e.`dept_id`;
/*+----+--------+------+--------+--------+--------+------------+---------+
| id | name | id | name | gender | salary | join_date | dept_id |
+----+--------+------+--------+--------+--------+------------+---------+
| 1 | 开发部 | 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 |
| 1 | 开发部 | 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 |
| 2 | 市场部 | 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 |
| 2 | 市场部 | 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 |
| 3 | 财务部 | 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 |
| 4 | 销售部 | NULL | NULL | NULL | NULL | NULL | NULL |
+----+--------+------+--------+--------+--------+------------+---------+*/
1.5 右外连接
右外连接:使用 RIGHT OUTER JOIN … ON,OUTER 可以省略
SELECT 字段名
FROM 左表 RIGHT [OUTER ]JOIN 右表
ON 条件
用右边表的记录去匹配左边表的记录,如果符合条件的则显示;否则,显示 NULL
可以理解为:在内连接的基础上保证右表的数据全部显示
#在员工表中增加一个员工
insert into emp values (null, '沙僧','男',6666,'2013-12-05',null);
select * from emp;
/*+----+--------+--------+--------+------------+---------+
| id | name | gender | salary | join_date | dept_id |
+----+--------+--------+--------+------------+---------+
| 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 |
| 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 |
| 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 |
| 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 |
| 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 |
| 6 | 沙僧 | 男 | 6666 | 2013-12-05 | NULL |
+----+--------+--------+--------+------------+---------+*/
#使用内连接查询
select * from
dept inner join emp
on dept.`id` = emp.`dept_id`;
/*+----+--------+----+--------+--------+--------+------------+---------+
| id | name | id | name | gender | salary | join_date | dept_id |
+----+--------+----+--------+--------+--------+------------+---------+
| 1 | 开发部 | 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 |
| 1 | 开发部 | 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 |
| 2 | 市场部 | 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 |
| 2 | 市场部 | 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 |
| 3 | 财务部 | 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 |
+----+--------+----+--------+--------+--------+------------+---------+*/
#使用右外连接查询
select * from
dept right join emp
on dept.`id` = emp.`dept_id`;
/*+------+--------+----+--------+--------+--------+------------+---------+
| id | name | id | name | gender | salary | join_date | dept_id |
+------+--------+----+--------+--------+--------+------------+---------+
| 1 | 开发部 | 1 | 孙悟空 | 男 | 7200 | 2013-02-24 | 1 |
| 2 | 市场部 | 2 | 猪八戒 | 男 | 3600 | 2010-12-02 | 2 |
| 2 | 市场部 | 3 | 唐僧 | 男 | 9000 | 2008-08-08 | 2 |
| 3 | 财务部 | 4 | 白骨精 | 女 | 5000 | 2015-10-07 | 3 |
| 1 | 开发部 | 5 | 蜘蛛精 | 女 | 4500 | 2011-03-14 | 1 |
| NULL | NULL | 6 | 沙僧 | 男 | 6666 | 2013-12-05 | NULL |
+------+--------+----+--------+--------+--------+------------+---------+*/
2. 子查询
什么是子查询
# 需求:查询开发部中有哪些员工
select * from emp;
#通过两条语句查询
select id from dept where name='开发部' ;
select * from emp where dept_id = 1;
#使用子查询
select * from emp
where dept_id =
(select id
from dept
where name='市场部'
);
子查询的概念:
- 一个查询的结果做为另一个查询的条件
- 有查询的嵌套,内部的查询称为子查询
- 子查询要使用括号
2.1 子查询结果的三种情况
- 1.子查询的结果是单行单列的:
- 子查询可以作为条件,使用运算符去判断。运算符∶>>=<<= =
- 2.子查询的结果是多行单列的∶
- 子查询可以作为条件,使用运算符in来判断
- 3.子查询的结果是多行多列的:
- 子查询可以作为一张虚拟表
情况一:子查询的结果是一个值的时候
子查询结果只要是单行单列,肯定在 WHERE 后面作为条件,父查询使用:比较运算符,如:> 、<、<>、=等
#如
SELECT 查询字段 FROM 表 WHERE 字段=(子查询);
#案例1:查询工资最高的员工是谁?
# 1)查询最高工资是多少
select max(salary) from emp;
# 2)根据最高工资到员工表查询到对应的员工信息
select * from emp where salary = (select max(salary) from emp);
#案例2:查询工资小于平均工资的员工有哪些?
# 1) 查询平均工资是多少
select avg(salary) from emp;
# 2) 到员工表查询小于平均的员工信息
select * from emp where salary < (select avg(salary) from emp);
情况二:子查询结果是多行单列的时候
子查询结果是单列多行,结果集类似于一个数组,父查询使用 IN 运算符
SELECT 查询字段
FROM 表
WHERE 字段
IN (子查询);
#案例一: 查询工资大于 5000 的员工,来自于哪些部门的名字
#1)先查询大于 5000 的员工所在的部门 id
select dept_id from emp where salary > 5000;
#2)再查询在这些部门 id 中部门的名字 Subquery returns more than 1 row
select name from dept where id = (select dept_id from emp where salary > 5000);
select name from dept where id in (select dept_id from emp where salary > 5000);
#案例二:查询开发部与财务部所有的员工信息
#1)先查询开发部与财务部的 id
select id from dept where name in('开发部','财务部');
#2)再查询在这些部门 id 中有哪些员工
select * from emp where dept_id in (select id from dept where name in('开发部','财务部'));
情况三:子查询的结果是多行多列
子查询结果只要是多列,肯定在 FROM 后面作为表
SELECT 查询字段
FROM (子查询) 表别名
WHERE 条件;
子查询作为表需要取别名,否则这张表没有名称则无法访问表中的字段
#案例一: 查询出 2011 年以后入职的员工信息,包括部门名称
#1) 在员工表中查询 2011-1-1 以后入职的员工
select * from emp where join_date >='2011-1-1';
#2) 查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门 id 等于的 dept_id
select *
from dept d, (select * from emp where join_date >='2011-1-1') e
where d.`id`= e.dept_id ;
#也可以使用表连接:
select *
from emp inner join dept
on emp.`dept_id` = dept.`id`
where join_date >='2011-1-1';
#或
select *
from emp inner join dept
on emp.`dept_id` = dept.`id` and join_date >='2011-1-1';
子查询小结
- 子查询结果只要是单列,则在 WHERE 后面作为条件
- 子查询结果只要是多列,则在 FROM 后面作为表进行二次查询
子查询练习题
-- 部门表
CREATE TABLE dept (
id INT PRIMARY KEY PRIMARY KEY, -- 部门id
dname VARCHAR(50), -- 部门名称
loc VARCHAR(50) -- 部门所在地
)DEFAULT CHARACTER SET utf8;
-- 添加4个部门
INSERT INTO dept(id,dname,loc) VALUES
(10,'教研部','北京'),
(20,'学工部','上海'),
(30,'销售部','广州'),
(40,'财务部','深圳');
SELECT * FROM dept;
-- 职务表,职务名称,职务描述
CREATE TABLE job (
id INT PRIMARY KEY,
jname VARCHAR(20),
description VARCHAR(50)
)DEFAULT CHARACTER SET utf8;
-- 添加4个职务
INSERT INTO job (id, jname, description) VALUES
(1, '董事长', '管理整个公司,接单'),
(2, '经理', '管理部门员工'),
(3, '销售员', '向客人推销产品'),
(4, '文员', '使用办公软件');
SELECT * FROM job;
-- 员工表
CREATE TABLE emp (
id INT PRIMARY KEY, -- 员工id
ename VARCHAR(50), -- 员工姓名
job_id INT, -- 职务id
mgr INT , -- 上级领导
joindate DATE, -- 入职日期
salary DECIMAL(7,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);
SELECT * FROM emp;
-- 工资等级表
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.查询所有员工信息。查询员工编号,员工姓名,工资,职务名称,职务描述
SELECT
t1.`id`, -- 员工编号
t2.`jname`, -- 员工姓名
t1.`salary`, -- 工资
t1.`ename`, -- 职务名称
t2.`description` -- 职务描述
FROM emp t1,job t2
WHERE t1.`job_id` = t2.`id`;
-- 2.查询员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
SELECT
t1.`id`, -- 员工编号
t2.`jname`, -- 员工姓名
t1.`salary`, -- 工资
t1.`ename`, -- 职务名称
t2.`description`, -- 职务描述
t3.`dname`,
t3.`loc`
FROM emp t1,job t2,dept t3
WHERE t1.`job_id` = t2.`id` AND t1.`dept_id`=t3.`id`;
-- 3.查询员工姓名,工资,工资等级
SELECT
t1.`ename`, -- 员工姓名
t1.`salary`, -- 工资
t2.`grade` -- 工资等级
FROM emp t1,salarygrade t2
WHERE t1.`salary` BETWEEN t2.`losalary` AND t2.`hisalary`;
-- 4.查询员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
SELECT
t1.`ename`, -- 员工姓名
t1.`salary`, -- 工资
t2.`jname`, -- 职务名称
t2.`description`, -- 职务描述
t3.`dname`, -- 部门名称
t3.`loc`, -- 部门位置
t4.`grade` -- 工资等级
FROM emp t1,job t2,dept t3,salarygrade t4
WHERE
t1.`job_id` = t2.`id`
AND t1.`dept_id` = t3.`id`
AND t1.`salary` BETWEEN t4.`losalary` AND t4.`hisalary`;
-- 5.查询出部门编号、部门名称、部门位置、部门人数
/*
分析:
1.部门编号、部门名称、部门位置--dept表。 部门人数--emp表
2.使用分组查询。按照emp.dept_id完成分组,查询count(id)
3.使用子查询将第2步的查询结果和dept表进行关联查询
*/
SELECT
t1.`id`,t1.`dname`,t1.`loc`,t2.total
FROM
dept t1,
(
SELECT
dept_id,COUNT(id) total
FROM emp
GROUP BY dept_id
) t2
WHERE t1.`id` = t2.dept_id;
-- 6.查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询
/*
分析:
1.姓名 --emp,直接上级的姓名--emp emp表的id和mgr是自关联
2.条件emp.id = emp.mgr
*/
SELECT
t1.`ename`,
t1.`mgr`,
t2.`id`,
t2.`ename`
FROM emp t1
LEFT JOIN emp t2
ON t1.`mgr` = t2.`id`;
3. 事务
3.1 事务的应用场景说明
什么是事务: 在实际的开发过程中,一个业务操作如:转账,往往是要多次访问数据库才能完成的。转账是一个用户扣钱,另一个用户加钱。如果其中有一条 SQL 语句出现异常,这条 SQL 就可能执行失败。事务执行是一个整体,所有的 SQL 语句都必须执行成功。如果其中有 1 条 SQL 语句出现异常,则所有的SQL 语句都要回滚,整个业务执行失败
转账的操作
-- 创建数据表
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('张三', 1000), ('李四', 1000);
模拟张三给李四转 500 元钱,一个转账的业务操作最少要执行下面的 2 条语句:
张三账号-500
李四账号+500
-- 张三账号-500
update account set balance = balance - 500 where name='张三';
-- 李四账号+500
update account set balance = balance + 500 where name='李四';
假设当张三账号上-500 元,服务器崩溃了。李四的账号并没有+500 元,数据就出现问题了。我们需要保证其中一条 SQL 语句出现问题,整个转账就算失败。只有两条 SQL 都成功了转账才算成功。这个时候就需要用到事务
3.2 手动提交事务
MySQL中可以有两种方式进行事务的操作:
- 手动提交事务
- 自动提交事务
手动提交事务的 SQL 语句
功能 | SQL 语句 |
---|---|
开启事务 | start transaction |
提交事务 | commit |
回滚事务 | rollback |
手动提交事务使用过程:
执行成功的情况: 开启事务 --> 执行多条 SQL 语句 --> 成功提交事务
执行失败的情况: 开启事务 --> 执行多条 SQL 语句 --> 事务的回滚
3.3 自动提交事务
MySQL 默认每一条 DML(增删改)语句都是一个单独的事务,每条语句都会自动开启一个事务,语句执行完毕自动提交事务,MySQL 默认开始自动提交事务
#查看MySQL是否开启自动提交事务
select @@autocommit;
/* @@表示全局变量,1 表示开启,0 表示关闭
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+*/
# 取消自动提交事务
set @@autocommit = 0;
MySQL数据库中事务默认自动提交
- 事务提交的两种方式:
- 自动提交:
- mysql就是自动提交的
- 一条DML(增删改)语句会自动提交一次事务
- 手动提交:
- Oracle 数据库默认是手动提交事务
- 需要先开启事务,再提交
- 自动提交:
- 修改事务的默认提交方式:
- 查看事务的默认提交方式:SELECT @@autocommit; – 1 代表自动提交 0 代表手动提交
- 修改默认提交方式: set @@autocommit = 0;
3.4 事务原理
事务开启之后, 所有的操作都会临时保存到事务日志中, 事务日志只有在得到 commit 命令才会同步到数据表中,其他任何情况都会清空事务日志(rollback,断开连接)
3.4.1 事务的步骤:
- 客户端连接数据库服务器,创建连接时创建此用户临时日志文件
- 开启事务以后,所有的操作都会先写入到临时日志文件中
- 所有的查询操作从表中查询,但会经过日志文件加工后才返回
- 如果事务提交则将日志文件中的数据写到表中,否则清空日志文件
3.4.2 回滚点
什么是回滚点
在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点
回滚点的操作语句
回滚点的操作语句 | 语句 |
---|---|
设置回滚点 | savepoint 名字 |
回到回滚点 | rollback to 名字 |
3.5 事务的隔离级别
3.5.1 事务的四大特性 ACID
事务特性 | 含义 |
---|---|
原子性(Atomicity) | 每个事务都是一个整体,不可再拆分,事务中所有的 SQL 语句要么都执行成功,要么都失败 |
一致性(Consistency) | 事务在执行前数据库的状态与执行后数据库的状态保持一致。如:转账前2个人的总金额是 2000,转账后 2 个人总金额也是 2000 |
隔离性(Isolation) | 事务与事务之间不应该相互影响,执行时保持隔离的状态 |
持久性(Durability) | 一旦事务执行成功,对数据库的修改是持久的。就算关机,也是保存下来的 |
- 事务的四大特征:
- 1.原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败。
- 2.持久性:当事务提交或回滚后,数据库会持久化的保存数据。
- 3.隔离性:多个事务之间。相互独立。
- 4.一致性:事务操作前后,数据总量不变
- 事务的隔离级别(了解)
- 概念:多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题。
- 存在问题:
- 1.脏读:一个事务,读取到另一个事务中没有提交的数据
- 2.不可重复读(虚读):在同一个事务中,两次读取到的数据不一样
- 3.幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改
- 隔离级别:
- 1.read uncommitted:读未提交
- 产生的问题:脏读、不可重复读、幻读
- 2.read committed:读已提交 (Oracle)
- 产生的问题:不可重复读、幻读
- 3.repeatable read:可重复读 (MySQL默认)
- 产生的问题:幻读
- 4.serializable:串行化
- 可以解决所有的问题
- 1.read uncommitted:读未提交
- 注意:隔离级别从小到大安全性越来越高,但是效率越来越低
- 数据库查询隔离级别:
- select @@tx_isolation;
- 数据库设置隔离级别:
- set global transaction isolation level 级别字符串;
- 演示:
- set global transaction isolation level read uncommitted;
- start transaction;
- – 转账操作
- update account set balance = balance - 500 where id = 1;
- update account set balance = balance + 500 where id = 2;
3.5.2 事务的隔离级别
事务在操作时的理想状态: 所有的事务之间保持隔离,互不影响。因为并发操作,多个用户同时访问同一个数据。可能引发并发访问的问题:
并发访问的问题 | 含义 |
---|---|
脏读 | 一个事务读取到了另一个事务中尚未提交的数据 |
不可重复读 | 一个事务中两次读取的数据内容不一致,要求的是一个事务中多次读取时数据是一致的,这是事务 update 时引发的问题 |
幻读 | 一个事务中两次读取的数据的数量不一致,要求在一个事务多次读取的数据的数量是一致的,这是 insert 或 delete 时引发的问题 |
3.5.3 MySQL 数据库有四种隔离级别
上面的级别最低,下面的级别最高。“是”表示会出现这种问题,“否”表示不会出现这种问题,隔离级别越高,性能越差,安全性越高
级别 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 |
---|---|---|---|---|---|
读未提交 | read uncommitted | 是 | 是 | 是 | |
读已提交 | read committed | 否 | 是 | 是 | Oracle 和 SQL Server |
可重复读 | repeatable read | 否 | 否 | 是 | MySQL |
串行化 | serializable | 否 | 否 | 否 |
3.6 MySQL事务隔离级别相关的命令
查询全局事务隔离级别
#查询隔离级别
select @@tx_isolation;
设置事务隔离级别,需要退出 MySQL 再重新登录才能看到隔离级别的变化
#设置隔离级别
set global transaction isolation level 级别字符串;
3.6.1 脏读的演示
1.将数据进行恢复:UPDATE account SET balance = 1000; 打开 A 窗口登录 MySQL,设置全局的隔离级别为最低 |
|
|
|
|
|
脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入 500 块,然后打电话给李四说钱已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四的再查看钱没了。
3.6.2 脏读问题的解决
解决脏读的问题: 将全局的隔离级别进行提升
将数据进行恢复:UPDATE account SET balance = 1000;
1. 在 A 窗口设置全局的隔离级别为 read committed |
|
|
|
|
|
|
结论:read committed 的方式可以避免脏读的发生
3.6.3 不可重复读的演示
将数据进行恢复:
UPDATE account SET balance = 1000;
1.开启 A 窗口 |
|
|
|
两次查询输出的结果不同,到底哪次是对的?不知道以哪次为准。 很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客户,结果在一个事务中针对不同的输出目的地进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。
3.6.4 不可重复读问题的解决
将全局的隔离级别进行提升为:repeatable read
将数据进行恢复:UPDATE account SET balance = 1000;
1. A 窗口设置隔离级别为:repeatable read |
|
|
|
结论:同一个事务中为了保证多次查询数据一致,必须使用 repeatable read 隔离级别
3.6.5 幻读的演示
在 MySQL 中无法看到幻读的效果
但我们可以将事务隔离级别设置到最高,以挡住幻读的发生
将数据进行恢复:UPDATE account SET balance = 1000;
1.开启 A 窗口 |
|
|
|
|
|
|
结论:使用 serializable 隔离级别,一个事务没有执行完,其他事务的 SQL 执行不了,可以挡住幻读
4. DCL
-
SQL分类:
- DDL:操作数据库和表
- DML:增删改表中数据
- DQL:查询表中数据
- DCL:管理用户,授权
-
DBA:数据库管理员
-
DCL:管理用户,授权
-
管理用户
-
添加用户:
- 语法:CREATE USER ‘用户名’@‘主机名’ IDENTIFIED BY ‘密码’;
-
删除用户:
- 语法:DROP USER ‘用户名’@‘主机名’;
-
修改用户密码:
UPDATE USER SET PASSWORD = PASSWORD(‘新密码’) WHERE USER = ‘用户名’;
UPDATE USER SET PASSWORD = PASSWORD(‘abc’) WHERE USER = ‘lisi’;SET PASSWORD FOR ‘用户名’@‘主机名’ = PASSWORD(‘新密码’);
SET PASSWORD FOR ‘root’@‘localhost’ = PASSWORD(‘123’);- mysql中忘记了root用户的密码?
-
cmd – > net stop mysql 停止mysql服务
- 需要管理员运行该cmd
-
使用无验证方式启动mysql服务: mysqld --skip-grant-tables
-
打开新的cmd窗口,直接输入mysql命令,敲回车。就可以登录成功
-
use mysql;
-
update user set password = password(‘你的新密码’) where user = ‘root’;
-
关闭两个窗口
-
打开任务管理器,手动结束mysqld.exe 的进程
-
启动mysql服务
-
使用新密码登录。
-
- mysql中忘记了root用户的密码?
-
查询用户:
– 1. 切换到mysql数据库
USE myql;
– 2. 查询user表
SELECT * FROM USER;- 通配符: % 表示可以在任意主机使用用户登录数据库
-
-
权限管理:
-
查询权限:
– 查询权限
SHOW GRANTS FOR ‘用户名’@‘主机名’;
SHOW GRANTS FOR ‘lisi’@‘%’; -
授予权限:
– 授予权限
grant 权限列表 on 数据库名.表名 to ‘用户名’@‘主机名’;
– 给张三用户授予所有权限,在任意数据库任意表上GRANT ALL ON . TO ‘zhangsan’@‘localhost’;
-
撤销权限:
– 撤销权限:
revoke 权限列表 on 数据库名.表名 from ‘用户名’@‘主机名’;
REVOKE UPDATE ON db3.account
FROM ‘lisi’@‘%’;
-
-
我们现在默认使用的都是 root 用户,超级管理员,拥有全部的权限。但是,一个公司里面的数据库服务器上面可能同时运行着很多个项目的数据库。所以,我们应该可以根据不同的项目建立不同的用户,分配不同的权限来管理和维护数据库。
注: mysqld 是 MySQL 的主程序,服务器端。mysql 是 MySQL 的命令行工具,客户端
- SQL分类∶
- 1.DDL:操作数据库和表
- 2.DML:增删改表中数据
- 3.DQL:查询表中数据
- 4.DCL:管理用户,授权
- DBA:数据库管理员
- DCL语句通常是由DBA来写的,DBA的权限非常高
SQL分类 | 语句 |
---|---|
DDL | create / alter / drop |
DML | nsert /update/delete |
DQL | select /show |
DCL | grant /revoke |
4.1 创建用户
语法
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
"参数"说明
“参数” | 说明 |
---|---|
‘用户名’ | 将要创建的用户名 |
‘主机名’ | 指定该用户在哪个主机上可以登陆,如果是本地用户可用 localhost,如果想让该用户可以从任意远程主机登陆,可以使用通配符% |
‘密码’ | 该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器 |
具体例子
创建 user1 用户,只能在 localhost 这个服务器登录 mysql 服务器,密码为 123
create user 'user1'@'localhost' identified by '123';
创建 user2 用户可以在任何电脑上登录 mysql 服务器,密码为 123
create user 'user2'@'%' identified by '123';
注: 创建的用户名都在 mysql 数据库中的 user 表中可以查看到,密码经过了加密
4.2 给用户授权
用户创建之后,没什么权限!需要给用户授权
语法
GRANT 权限 1, 权限 2... ON 数据库名.表名 TO '用户名'@'主机名';
"参数"说明
“参数” | 说明 |
---|---|
GRANT…ON…TO | 授权关键字 |
权限 | 授予用户的权限,如 CREATE、ALTER、SELECT、INSERT、UPDATE 等。如果要授予所有的权限则使用 ALL |
数据库名.表名 | 该用户可以操作哪个数据库的哪些表。如果要授予该用户对所有数据库和表的相应操作权限则可用表示,如.* |
‘用户名’@‘主机名’ | 给哪个用户授权,注:有 2 对单引号 |
具体例子
给 user1 用户分配对 test 这个数据库操作的权限:创建表,修改表,插入记录,更新记录,查询
grant create,alter,insert,update,select on test.* to 'user1'@'localhost';
#注:用户名和主机名要与上面创建的相同,要加单引号
给 user2 用户分配所有权限,对所有数据库的所有表
grant all on *.* to 'user2'@'%';
4.3 撤销授权
语法
REVOKE 权限 1, 权限 2... ON 数据库.表名 revoke all on test.* from 'user1'@'localhost'; '用户名'@'主机名';
关键字 | 说明 |
---|---|
REVOKE…ON…FROM | 撤销授权的关键字 |
权限 | 用户的权限,如 CREATE、ALTER、SELECT、INSERT、UPDATE 等,所有的权限则使用 ALL |
数据库名.表名 | 对哪些数据库的哪些表,如果要取消该用户对所有数据库和表的操作权限则可用表示,如.* |
‘用户名’@‘主机名’ | 给哪个用户撤销 |
具体例子
撤销 user1 用户对 test 数据库所有表的操作的权限
revoke all on test.* from 'user1'@'localhost';
# 注:用户名和主机名要与创建时相同,各自要加上单引号
4.4 查看权限
语法
SHOW GRANTS FOR '用户名'@'主机名';
具体例子
查看 user1 用户的权限
注: usage 是指连接(登陆)权限,建立一个用户,就会自动授予其 usage 权限(默认授予)
4.5 删除用户
语法
DROP USER '用户名'@'主机名';
4.6 修改管理员密码
语法
mysqladmin -uroot -p password 新密码
注意: 需要在未登陆 MySQL 的情况下操作,新密码不需要加上引号
具体例子
- 将 root 管理员的新密码改成 123456
- 要求输入旧密码
- 使用新密码登录
4.7 修改普通用户密码
语法
set password for '用户名'@'主机名' = password('新密码');
# 注意:需要在登陆 MySQL 的情况下操作,新密码要加单引号
具体例子
将’user1’@‘localhost’的密码改成’666666’
使用新密码登录,老密码登录不了
set password for 'user1'@'localhost'=password('66666')
说明:本内容整理自B站黑马程序员Java web课程视频及文档>>B站黑马程序员Java web课程视频