阶段三-MySQL-Day02~Day03

一、DQL查询表中数据

数据查询语言(Data Query Language,DQL):DQL主要用于数据的查询,其基本结构是使用SELECT子句,FROM子句和WHERE子句的组合来查询一条或多条数据。

DQL是实际开发过程中使用最多的。

1. 数据准备

# 创建员工表 
表名 emp 
表中字段: 
eid 员工id,int 
ename 姓名,varchar(20) 
sex 性别,char(1) 
salary 薪资,double(7,1)
hire_date 入职时间,date
dept_name 部门名称,varchar(20)
# 创建lsh02数据库
create database lsh02;

# 选择lsh02
use lsh02;

# 创建员工表
create table emp(
	eid int,
	ename varchar(20),
	sex char(1),
	salary double(7,1),
	hire_date date,
	dept_name varchar(20)
);
# 添加测试数据 
insert into emp VALUES(1,'孙悟空','男',7200,'2013-02-04','教学部'); 
insert into emp VALUES(2,'猪八戒','男',3600,'2010-12-02','教学部');
insert into emp VALUES(3,'唐僧','男',9000,'2008-08-08','教学部'); 
insert into emp VALUES(4,'白骨精','女',5000,'2015-10-07','市场部'); 
insert into emp VALUES(5,'蜘蛛精','女',5000,'2011-03-14','市场部'); 
insert into emp VALUES(6,'玉兔精','女',200,'2000-03-14','市场部'); 
insert into emp VALUES(7,'林黛玉','女',10000,'2019-10-07','财务部');
insert into emp VALUES(8,'黄蓉','女',3500,'2011-09-14','财务部'); 
insert into emp VALUES(9,'吴承恩','男',20000,'2000-03-14',null); 
insert into emp VALUES(10,'孙悟饭','男', 10,'2020-03-14','财务部');
insert into emp VALUES(11,'兔八哥','女', 300,'2010-03-14','财务部');
insert into emp VALUES(12,'Tom','男', null,'2010-03-14','财务部');

2. 简单查询

查询不会对数据库中的数据进行修改, 只是一种显示数据的方式。

-- 简单查询
-- 1.查询emp中所有的数据
select * from emp;
-- 2.查询emp表中所有的记录, 只显示eid和ename字段
select eid,ename from emp;
-- 3.查询所有的员工信息, 将字段名显示为中文
-- as可以省略
select
	eid as'编号',
	ename as '姓名',
	sex as '性别',
	salary as '薪资',
	hire_date as '入职日期',
	dept_name as '部门名称'
from emp;
-- 4.查询有多少个部门
select dept_name from emp;
-- 需要去重 null也会保存
select distinct dept_name from emp;
-- 5.将所有的员工薪资加1000显示
select *,salary+1000 from emp;

3. 条件查询

  • 如果查询语句中没有设置条件, 就会查询所有的行信息

  • 在实际应用中, 通常会指定查询的条件, 对记录进行过滤

3.1 比较运算符
运算符说明
> < <= >= = <> !=大于 小于 小于等于 大于等于 等于 不等于
between...and...显示在某一区间的值
in(集合)集合表示多个值,使用逗号分隔,例如name in (悟空,八戒); in中的每个数据都会作为一次条件,只要满足条件就会显示
like '%张%'模糊查询
is null查询某一列为null的值,注意,不能写 = null
3.2 逻辑运算符
运算符说明
and &&多个条件同时成立
or ||多个条件任一成立
not取反
-- 条件查询
-- 精确查询
#1 查询员工姓名为黄蓉的员工信息 
select * from emp where ename="黄蓉";

#2 查询薪水价格为5000的员工信息
select * from emp where salary=5000;

#3 查询薪水价格不是5000的所有员工信息 
select * from emp where salary!=5000;
select * from emp where salary<>5000;

#4 查询薪水价格大于6000元的所有员工信息 
select * from emp where salary>6000;

#5 查询薪水价格在5000到10000之间的员工信息 
select * from emp where salary >= 5000 and salary <= 10000; 
select * from emp where salary between 5000
and 10000;

#6 查询薪水价格是3600或7200或者20000的员工信息
select * from emp where salary=3600 or salary=7200 or
salary=20000 ;
select * from emp where salary in(3600,7200,20000);

案例2:

模糊查询

模糊查询,通配符

通配符说明
%表示匹配任意多个字符
_表示匹配任意一个字符
-- 模糊查询
-- like
-- %:0~n个
-- _:1个
#1 查询含有'八'字的员工信息
select * from emp where ename like "%八%";

#2 查询以'孙'字开头的员工信息 
select * from emp where ename like '孙%';

#3 查询第二个字为'兔'的员工信息 
select * from emp where ename like '_兔%';
-- null值不能使用=,而是使用is和is not
#4 查询没有部门的员工信息
select * from emp where dept_name is null;

#5 查询有部门的员工信息
select * from emp where dept_name is not null;

4. 排序

通过order by子语句, 可以将查询出的结果进行排序(排序只是显示效果, 并不会影响真实的数据)。

-- 语法格式:
select 字段名 from 表名 [where 条件] order by 字段名[asc | desc];
-- asc  升序(默认)
-- desc 降序
4.1 单列排序

只按照某一个字段进行排序。

案例:

查询所有的员工信息, 使用salary进行排序。

-- 升序排序(默认 asc)
select * from emp order by salary;

-- 降序排序(desc)
select * from emp order by salary desc;
4.2 组合排序

同时对多个字段进行排序, 如果第一个字段相同, 就按照第二个字段排序, 以此类推。

案例:

查询所有的员工信息, 使用salary升序排列,salary相同按照入职日期降序排序。

-- 组合排序
select * from emp order by salary, hire_date desc;

5. 函数

MySQL中提供了大量函数来简化用户对数据库的操作,比如字符串的处理、日期的运算、数值的运算等等。使用函数可以大大提高SELECT语句操作数据库的能力,同时也给数据的转换和处理提供了方便。

函数只是对查询结果中的数据进行处理,不会改变数据的值。

单行函数:

单行函数是指对每一条记录的值进行计算,并得到相应的计算结果,然后返回给用户,也就是说,每条记录作为一个输入参数,经过函数计算得到每条记录的计算结果。

常用的单行函数主要包括字符串函数、数值函数、日期与时间函数、流程函数以及其他函数。

使用单行函数, 是对行中字段的操作, 操作多少行, 返回多少行数据。

多行函数:

我们之前做的查询都是横向查询,它们都是根据条件一行一行的进行判断,而使用多行函数查询是纵向查询,它是对某一列的值进行计算,然后返回一个单一的值。

  • 多行函数会忽略null空值。

  • 多行函数也称为分组函数, 聚合函数。

使用多行函数,是对某一列的值进行计算,然后返回一个单一的值

5.1 单行函数

5.1.1 字符函数

大小写处理函数

函数描述实例
LOWER(s)|LCASE(s)将字符串 s 转换为小写将字符串 OLDLU转换为小写:SELECT LOWER("OLDLU"); -- oldlu
UPPER(s)|UCASE(s)将字符串s转换为大写将字符串 oldlu转换为大写:SELECT UPPER("oldlu"); -- OLDLU

字符处理函数

函数描述实例
LENGTH(s)返回字符串 s 的长度返回字符串oldlu的字节数SELECT LENGTH("oldlu"); -- 5;
CONCAT(s1,s2...sn)字符串 s1,s2 等多个字符串合并为一个字符串合并多个字符串SELECT CONCAT("sxt ", "teacher ", "oldlu"); -- sxt teacher oldlu;
LPAD(s1,len,s2)在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len将字符串 x 填充到 oldlu字符串的开始处:SELECT LPAD('oldlu',8,'x'); -- xxxoldlu
LTRIM(s)去掉字符串 s 开始处的空格去掉字符串 oldlu开始处的空格:SELECT LTRIM(" oldlu") ;-- oldlu
REPLACE(s,s1,s2)将字符串 s2 替代字符串 s 中的字符串 s1将字符串 oldlu 中的字符 o 替换为字符 O:SELECT REPLACE('oldlu','o','O'); -- Oldlu
REVERSE(s)将字符串s的顺序反过来将字符串 abc 的顺序反过来:SELECT REVERSE('abc'); -- cba
RPAD(s1,len,s2)在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len将字符串 xx填充到 oldlu字符串的结尾处:SELECT RPAD('oldlu',8,'x'); -- oldluxxx
RTRIM(s)去掉字符串 s 结尾处的空格去掉字符串 oldlu 的末尾空格:SELECT RTRIM("oldlu "); -- oldlu
SUBSTR(s, start, length)从字符串 s 的 start 位置截取长度为 length 的子字符串从字符串 OLDLU中的第 2 个位置截取 3个 字符:SELECT SUBSTR("OLDLU", 2, 3); -- LDL
SUBSTRING(s, start, length)从字符串 s 的 start 位置截取长度为 length 的子字符串从字符串 OLDLU中的第 2 个位置截取 3个 字符:SELECT SUBSTRING("OLDLU", 2, 3); --LDL
TRIM(s)去掉字符串 s 开始和结尾处的空格去掉字符串 oldlu 的首尾空格:SELECT TRIM(' oldlu ');-- oldlu
-- 字符处理函数
/*
	utf-8字符集,汉字占三个字节,字母占1个字节
	length返回字符串的字节数
*/
select length(ename),ename from emp;

-- concat拼接字符串
select concat("姓名",ename) from emp;
select concat(ename,"姓名") from emp;

-- LPAD,RPAD填充字符串
-- 左边
select lpad(ename,5,'a') from emp;
-- 右边
select rpad(ename,5,'a') from emp;

-- LTRIM,RTRIM去除空格
-- 去除左边的空格
select ltrim(ename) from emp;
-- 去除右边的空格
select rtrim(ename) from emp;
-- 去除两边的空格
select trim(ename) from emp;

-- REPLACE替换
select replace('Old','O','o') from dual;

-- reverse反转
select reverse('Old');

-- substring()和substr()一样
-- 截取角标从1开始
select substr(ename,1,2) from emp;
select substring(ename,1,2) from emp;
5.1.2 数值函数
函数描述实例
ABS(num)返回num的绝对值SELECT ABS(-1) -- 返回1
CEIL(num)返回大于num的最小整数(向上取整)SELECT CEIL(1.5) -- 返回2
FLOOR(num)返回小于num的最大整数(向下取整)SELECT FLOOR(1.5) -- 返回1
MOD(num1, num2)返回num1/num2的余数(取模)SELECT MOD(5,2) -- 1
PI()返回圆周率的值SELECT MOD(5,2) -- 1
POW(num,n)/POWER(num, n)返回num的n次方SELECT POW(2,3) -- 8
RAND(num)返回0~1之间的随机数SELECT RAND() --0.93099315644334
ROUND(num, n)返回x四舍五入后的值,该值保留到小数点后n位SELECT ROUND(1.23456,3) --1.235
TRUNCATE(num, n)返回num被舍去至小数点后n位的值(与 ROUND 最大的区别是不会进行四舍五入)SELECT TRUNCATE(1.23456,3) -- 1.234

MySQL提供了一张虚拟表,该表名为“dual”,是MySQL为了满足用“SELECT ••• from•••”的习惯而增设的一张虚拟表。 在使用dual表时,如果没有where子句,则可以省略“from dual”, 没有实际意义, 查询的字段不属于任何表的时候, 就可以使用dual这张虚拟表。

-- 数值函数
select 
abs(-1), #返回绝对值
ceil(1.4), #向上取整
floor(1.6), #向下取整
mod(1,2), #取余
pi(), #圆周率
pow(2,2), #2的2次方=power()
rand(4), #0-1的随机数
round(2.565,2), # 保留2.564小数2位,四舍五入
truncate(2.565, 2), # 保留2.564小数2位,直接舍去
sqrt(25) #开根号
;
#这里默认使用的是虚表dual,可以省略不写
5.1.3 日期与时间函数
函数描述实例
CURDATE()返回当前日期SELECT CURDATE(); -- 2022-07-20
CURTIME()返回当前时间SELECT CURTIME(); -> 19:59:02
NOW()返回当前日期和时间SELECT NOW() -> 2022-07-20 20:57:43
SYSDATE()返回该函数执行时的日期和时间SELECT SYSDATE() -> 2022-07-20 20:57:43
DAYOFYEAR(date)返回日期date为一年中的第几天SELECT DAYOFYEAR('2011-11-11 11:11:11') ->315
WEEK(date)/WEEKOFYEAR(date)返回日期date为一年中的第几周SELECT DAYOFWEEK('2011-11-11 11:11:11') ->6
DATEDIFF(date1, date2)返回起始日期date1与结束日期date2之间的间隔天数SELECT DATEDIFF('2001-01-01','2001-02-02') -> -32
DATE()从日期或日期时间表达式中提取日期值SELECT DATE("2017-06-15"); -> 2017-06-15
DAY(d)返回日期值 d 的日期部分SELECT DAY("2017-06-15"); -> 15
MONTH(d)返回日期d中的月份值,1 到 12SELECT MONTH('2011-11-11 11:11:11') ->11
YEAR(d)返回年份SELECT YEAR("2017-06-15"); -> 2017
-- 日期与时间函数
select
curdate(), #返回当前日期
curtime(), #返回当前时间
now(), #返回当前日期和时间
sysdate(), #返回该函数执行时的日期和时间
dayofyear('1990-1-1'), #返回日期为一年中的第几天
week('1990-1-1'),
weekofyear('1990-1-1'),
#返回日期为一年中的第几周
datediff('1990-1-1','19990-1-2'),
#返回两个日期之间的差值
sleep(2)
;
流程控制函数
间隔类型描述
IF(condition, t, f)如果条件condition为真,则返回t,否则返回f
IFNULL(value1, value2)如果value1不为null,则返回value1,否则返回value2
NULLIF(value1, value2)如果value1等于value2,则返回null,否则返回value1
CASE WHEN [condition1] THEN result1 [WHEN [condition2] THEN result2 ...] [ELSE result] END如果条件condition1为真,则返回result1,···,否则返回result
# 流程空值函数
#1 查询emp表所有数据, 薪资 >= 10000 高工资  其他 低工资
# if(条件,语句一,语句二)
# 条件成立语句一,否则语句二
select *,if(salary >= 10000,
'高工资','低工资') 
from emp;

#2 查询emp表所有数据, 计算出员工的年薪 薪资*12 加年终奖(每人30k)空值算30K
select salary'月薪',
salary*12+30000 '年薪'
from emp;#空值还是空值
-- 优化
select salary'月薪',
ifnull(salary,0)
*12+30000 '年薪'
from emp;

#3 查询emp表所有数据, 薪资 >=3000 加把劲  >=5000 加油哦  >=9000 坚持住 >= 15000 优秀  其他 不及格
select eid, ename, salary, 
	case
		when salary >= 15000 then '优秀'
		when salary >= 9000 then '坚持住'
		when salary >= 5000 then '加油哦'
		when salary >= 3000 then '加把劲'
		else '努力奋斗吧骚年'
	end
from emp;
5.1.5 其它函数
函数描述
DATABASE()返回当前数据库名
VERSION()返回当前MySQL的版本号
USER()返回当前登录的用户名
INET_ATON(IP)返回IP地址的数字表示
INET_NTOA返回数字代表的IP地址
PASSWORD(str)实现对字符串str的加密操作
FORMAT(num, n)实现对数字num的格式化操作,保留n位小数
CONVERT(data, type)实现将数据data转换成type类型的操作
-- 其他函数
select
database(), #返回当前数据库名
user(), #返回登陆的用户名
version(),#返回mysql版本号
inet_aton("192.168.10.1"),
#返回ip的十进制数字
inet_ntoa(3232238081);
#返回数字代表的IP地址
5.2 多行函数
  • 多行函数会忽略null空值。

  • 多行函数也称为分组函数, 聚合函数。

多行函数作用
count(字段)统计指定列不为null的记录行数
sum(字段)计算指定列的数值和
max(字段)计算指定列的最大值
min(字段)计算指定列的最小值
avg(字段)计算指定列的平均值
-- 多行函数
-- 多行函数会忽略空值
#1 查询员工的总数
-- 使用某个字段查询,聚合函数会忽略null, 需要注意为null的字段,一般是主键
select count(eid) from emp;
-- 所有字段匹配查询
select count(*) from emp; 
-- 增加一列1
select 1 from emp;
-- 可以用这个1来算总数
select count(1) from emp;

#2 查看员工总薪水、最高薪水、最小薪水、薪水的平均值, 显示为总薪水, ... 
select
sum(salary) '总薪水',
max(salary) '最高薪水',
min(salary) '最低薪水',
avg(salary) '平均薪水'
from emp
;

#3 查询薪水大于4000员工的个数 
select count(1) from emp
where salary > 4000;

#4 查询部门为'教学部'的所有员工的个数 
select count(1) from emp
where dept_name = '教学部';

#5 查询部门为'市场部'所有员工的平均薪水
select avg(salary) from emp where dept_name = '市场部';

#6 查询部门的个数
select dept_name from emp;  
-- 12个
select count(dept_name) from emp; 
-- 11个 多行函数会忽略null

-- 部门去重
select distinct dept_name from emp;
select distinct dept_name from emp
where dept_name is not null;
select count(distinct dept_name) from emp;
5.3 分组

分组查询指的是使用group by语句, 对查询的信息进行分组, 相同数据作为一组。

语法格式:

select 分组字段/聚合函数 from 表名 group by 分组字段 [having 条件];

 需求1: 通过性别分组

# 按照性别分组查询
select * from emp group by sex;    -- 能查到结果, 但是没有意义
select sex from emp group by sex;  -- 正确操作

 分析:group by分组过程。

注意事项:

  • 分组时可以查询要分组的字段, 或者使用聚合函数进行统计操作

  • 查询其他字段没有意义

6. 经典面试题

MySQL行列转置/MySQL行列转换(笔试题)

需求:

把下面数据转换

转换成下面效果

创建表及测试数据:

create table student(
	id int(11) primary key auto_increment,
	name varchar(20),
	subject varchar(20),
	score double
);

insert into student values(1,'张三','语文',20);
insert into student values(2,'张三','数学',30);
insert into student values(3,'张三','英语',40);
insert into student values(4,'李四','语文',50);
insert into student values(5,'李四','数学',60);
insert into student values(6,'李四','英语',70);
use zqwl1;
create table student(
	id int(11) primary key auto_increment,
	name varchar(20),
	subject varchar(20),
	score double
);

insert into student values(1,'张三','语文',20);
insert into student values(2,'张三','数学',30);
insert into student values(3,'张三','英语',40);
insert into student values(4,'李四','语文',50);
insert into student values(5,'李四','数学',60);
insert into student values(6,'李四','英语',70);

select * from student;

-- 第一种
select 
max(if(subject='语文',score,0)) '语文',
max(if(subject='数学',score,0)) '数学',
max(if(subject='英语',score,0)) '英语',
name
from student group by name;

-- 第二种
select 
max(
case subject
when '语文'
then score
else 0
end) '语文',
max(
case subject
when '数学'
then score
else 0
end) '数学',
max(
case subject
when '英语'
then score
else 0
end) '英语',
name
from student group by name;






7. limit关键字

作用:

  • limit是限制的意思, 限制返回的查询结果的函数(通过limit函数,控制查询返回多少行数据)

  • limit 语法是 MySql的方言, 用来完成分页

语法格式:

select 字段1, 字段2 ... from 表名 limit offset, length;

参数说明

  1. offset 起始行数, 从0开始, 如果省略则默认从0开始, 0代表MySQL中第一条数据

  2. length 返回的行数


-- limit关键字
#1 查询emp表中的前5条数据
select * from emp limit 5;-- 默认角标从0开始
select * from emp limit 1,5;-- 角标从1查询5条
#2 查询emp表中 从第4条开始, 查询6条
select * from emp limit 3, 6; -- 从0开始, 所以第四条数据为3

二、SQL执行流程

1. 介绍

前面讲解SQL语句中的各个关键字时,介绍过SQL语句的执行步骤,接下来,我们来学习在数据库管理系统中SQL语句到底是怎么执行的。

2. SQL语句执行流程

2.1 执行流程图示

2.2 各个组件介绍
  1. 连接管理与安全验证:MySQL有连接池(Connection Pool)管理客户端的连接。客户端连接后会验证用户名、密码、主机信息等

  2. 缓存(Cache&Buffer):缓存中存储了SQL命令的HASH,直接比对SQL命令的HASH和缓存中key是否对应,如果对应,直接返回结果,不再执行其他操作。由于缓存的是SQL的HASH,所以根据Hash特性SQL中空格等内容必须完全一样。缓存里面包含表缓存、记录缓存、权限缓存等。查询语句执行完成后会把查询结果缓存到缓存中。在MySQL中查询缓存默认不开启。考虑到查询缓存性能瓶颈问题,从MySQL8开始已经不支持查询缓存了。

  3. 解析器(Parser)主要作用是解析SQL命令。将SQL命令分解成数据结构(解析树),后续的操作都是基于这个结构的。如果在分解过程中遇到错误,出现SQL解析错误。解析时主要检查SQL中关键字,检查关键字是否正确、SQL中关键字顺序是否正确、引号是否对应是否正确等。

  4. 预处理器:根据解析器的解析树,进一步检查表是否存在、列是否存在、名字和别名是否有歧义等。

  5. 优化器(Optimizer):根据官网说明在执行SQL查询时会根据开销自动选择最优查询方案。采用“选择-投影-连接”的策略。先选择where中的行数。同时先选择要选择的列,而不是全部列,最后把内容合并到一起。

  6. 执行器:包含执行SQL命令。获取返回结果。生成执行计划等。

  7. 存储引擎:访问物理文件的媒介

2.3 执行流程详细说明
  1. 客户端向服务器端发送SQL命令和连接参数

  2. 服务器端连接模块连接并验证

  3. 缓存模块解析SQL为Hash并与缓存中Hash表对应。如果有结果直接返回结果,如果没有对应继续向下执行。如果是MySQL 8 是没有查询缓存的。

  4. 解析器解析SQL为解析树,检查关键字相关问题,如果出现错误,报SQL解析错误。如果正确,继续执行

  5. 预处理器对解析树继续处理检查表、列别名等,处理成功后生成新的解析树。

  6. 优化器根据开销自动选择最优执行计划,生成执行计划

  7. 执行器执行执行计划,访问存储引擎接口

  8. 存储引擎访问物理文件并返回结果

  9. 如果开启查询缓存,缓存管理器把结果放入到查询缓存中。

  10. 返回结果给客户端

三、多表

1. 多表的概述

实际开发中, 一个项目通常需要很多张表才能完成。

例如, 一个商城项目的数据库, 需要很多张表, 用户表, 分类表, 商品表, 订单表...

2. 单表的缺点

2.1 单表存在的问题

冗余, 同一个字段出现大量重复的数据。

设计为两张表

  1. 多表方式设计 employee 员工表: eid ename age

    department 部门表: depid, dep_name, dep_location

  2. 创建员工和部门表

-- 创建员工表
create table employee(
	eid int primary key auto_increment,
	ename varchar(10),
	age int,
    dep_id int
);
-- 创建部门表
create table department(
	dep_id int primary key auto_increment,
	dep_name varchar(10),
	dep_location varchar(10)
);

插入数据

-- 添加2个部门
insert into department values(default, '研发部','广州'),(default, '销售部', '深圳');
select * from department;
-- 添加5个人员工
insert into employee values(default, '贾宝玉', 20, 1), 
						   (default, '林黛玉', 21, 1),
					       (default, '薛宝钗', 20, 1),
						   (default, '元春', 20, 2),
						   (default, '探春', 22, 2),
						   (default, '甄士隐', 18, 2);
select * from employee;
3.1 表关系分析

3.2 多表设计上的问题

当我们在员工表的dep_id里面输入了不存在的部门编号, 数据依然可以添加, 显然这是不合理的。

为了解决这个问题,我们可以使用外键约束.

3.3 外键约束
  1. 外键

    外键指的是在主表中与从表主键对应的的那个字段, 如员工表的dep_id, 就是外键。

  2. 外键约束

         使用外键约束可以让两张表之间产生一个对应关系, 从而保证主从表数据的完整性。

3.3.1 创建外键约束
-- 创建外键约束
/*
		第一种:
			新建表时添加外键约束:
			constraint 外键名称(随便写) 
			foreign key(外键字段名)
			references 主表名称(主表字段名);
		第二种:
			为已经创建好的表添加
			alter table 从表名 add 	
			constraint 外键名称(随便写) 
			foreign key(外键字段名)
			references 主表名称(主表字段名);

*/
drop table employee;
create table employee(
	eid int primary key auto_increment,
	ename varchar(10),
	age int,
  dep_id int,
	constraint emp_dept_fk foreign key(dep_id)
	references department(dep_id)
);

-- 插入正常数据(从表的外键对应着主表的主键)
insert into employee values(default, '张百万', 20, 1), (default, '艳秋', 22, 2);

-- 插入不存在的dep_id
insert into employee values(default, '张亿万', 20, 100);
-- 报错:Cannot add or update a child row: a foreign key constraint fails
3.3.2 删除外键约束

3.4 外键约束的注意事项

1.从表的外键类型必须和主表的主键类型保持一致

2.添加或修改从表数据时,必须在主表的主键中存在

3.删除和变更从表的数据时,先删除从表中的数据或将外键中的值设置为null,再删除主表中的数据

不能先删除主表中的数据

 4.可以设置外键约束再修改或删除时的联动操作

使用说明:

选项作用
RESTRICT(默认)如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作
No action相当于RESTRICT, 都是立即检查外键约束
CASCADE在主表上update/delete记录时,同步update/delete掉从表匹配的记录
SET NULL在主表上update/delete记录时,将子表上匹配记录的列设为null 注意: 子表的外键列不能为not null

四、多表关系设计

1. 介绍

实际的开发过程中, 一个项目通常需要很多张表才能完成.

表与表之间的3种关系举例
一对多关系(最常见)员工表和部门表 学生表和班级表
多对多关系学生表和课程表 用户表和角色表
一对一关系(使用较少)一对一的关系可以设计成一张表

2. 一对多关系(常见)

  • 一对多关系(1 : n) 例如: 班级和学生, 部门和员工, 客户和订单, 类别和商品

  • 一对多建表原则 在从表(多方)创建一个字段, 该字段作为外键指向主表的主键

3. 多对多关系

  • 多对多关系(m : n) 例如: 老师和学生, 学生和课程, 用户和角色。

  • 多对多关系建表原则 多对多的关系不能直接处理, 需要创建第三张表, 也称为中间表, 中间表至少两个字段,这两个字段分别作为外键指向各自一方的主键, 实际就是将多对多拆分为两个一对多。

4. 一对一关系

在实际的开发过程中应用不多, 因为一对一的关系可以设计为一张表。

五、多表查询

1. 什么是多表查询

  • DQL: 查询多张表, 获取到需要的数据

2. 数据准备

创建分类表与商品表

#分类表 (一方 主表)
create table category (
	cid varchar(32) primary key,
	cname varchar(50)
);
#商品表 (多方 从表)
create table products(
	pid varchar(32) primary key,
	pname varchar(50),
	price int,
	flag varchar(2),  # 是否上架标记为:1表示上架、0表示下架
	cid varchar(32),
	-- 添加外键约束
	foreign key (cid) references category (cid)
);

插入数据

#分类数据
insert into category(cid,cname) values('c001','家电');
insert into category(cid,cname) values('c002','鞋服');
insert into category(cid,cname) values('c003','化妆品');
insert into category(cid,cname) values('c004','汽车');

#商品数据
insert into products values('p001','小米电视机',5000,'1','c001');
insert into products values('p002','格力空调',3000,'1','c001');
insert into products values('p003','美的冰箱',4500,'1','c001');
insert into products values('p004','篮球鞋',800,'1','c002');
insert into products values('p005','运动裤',200,'1','c002');
insert into products values('p006','T恤',300,'1','c002');
insert into products values('p007','冲锋衣',2000,'1','c002');
insert into products values('p008','神仙水',800,'1','c003');
insert into products values('p009','大宝',200,'1','c003');

3. 笛卡尔积

交叉连接(CROSS JOIN)是对两个或者多个表进行笛卡儿积操作,所谓笛卡儿积就是关系代数里的一个概念,表示两个表中的每一行数据任意组合的结果。

笛卡尔积不管是否匹配,都连接。没有实际意义,有理论意义 。

笛卡尔积便于理解连接查询的原理。

语法格式:

4. 多表查询的分类

4.1 内连接查询(等值连接)
  • 通过指定的条件去匹配两张表中的数据, 匹配上就显示, 匹配不上就不显示

  • 如: 按照从表的外键 = 主表的主键进行匹配 | inner join on, inner可以省略

4.2 自连接查询

自连接就是某张表自己和自己关联。

4.3 非等值连接

非等值连接就是两张表进行关联的时候不是使用关联的id相等进行连接的。

-- 内连接查询
-- 特点:只显示条件匹配的数据,不匹配的不显示

-- 等值连接

-- SQL92隐式内连接
# 查找商品分类后的信息
select pname,price,flag,cname from products p,category c where p.cid = c.cid;
-- SQL99显示内连接
# 查找商品分类后的信息
-- inner可以省略
select pname,price,flag,cname from products p inner join category c 
on p.cid = c.cid;
# 查询鞋服分类下, 价格大于500的商品名称和价格以及对应的分类名称
select pname,price,cname from products p inner join category c 
on p.cid = c.cid  having cname='鞋服' and price > 500;

-- 自连接
-- 某张表自己和自己连接
-- 查询员工名称和上级领导的名称
select * from emp;
-- SQL92
select c1.ename '员工',c2.ename '领导' from emp c1,emp c2 where c1.mgr=c2.empno;
-- SQL99
select c1.ename '员工',c2.ename '领导' from emp c1 join emp c2 
on c1.mgr=c2.empno;


-- 非等值连接
-- 条件不是等值的条件
select * from emp;
select * from salgrade;
-- SQL92
-- 查询员工的工资和工资级别
select e.ename,s.grade from emp e,salgrade s where e.sal between s.losal and s.hisal;
-- SQL99
-- 查询员工的工资和工资级别
select e.ename,s.grade from emp e join salgrade s on e.sal between s.losal and s.hisal;
4.4 外连接查询

通过指定的条件去匹配两张表中的数据, 匹配上就显示, 匹配不上也可以显示。

外连接只有SQL99可以使用

4.4.1 左外连接

左外连接的特点:

  1. 以左表为主, 左表中的数据全部显示

  2. 右表匹配到数据就显示匹配到的数据

  3. 右表没有匹配的数据显示为null

4.4.2 右外连接

右外连接的特点:

  1. 右表为主, 右表中的数据全部显示

  2. 左表匹配到数据就显示匹配到的数据

  3. 左表没有匹配的数据显示为null

4.4.3 全外连接

注意: MySQL 中不支持 FULL OUTER JOIN 连接

可以使用 union 实现全完连接。

  • UNION: 可以将两个查询结果集合并,返回的行都是唯一的,会去重.

  • UNION ALL: 只是简单的将两个结果合并后就返回。

-- 外连接
-- 左外连接
-- 以左表为主,左表的所有数据都展示,右表没有匹配的显示为null
#查询分类信息以及对应的商品信息
select * from category c left join products p on c.cid=p.cid;

-- 右外连接
-- 以右表为主,右表的所有数据都展示,左表没有匹配的显示为null
#查询分类信息以及对应的商品信息
select * from products p right join category c on c.cid = p.cid;

-- 全外连接
-- MySQL中不能使用全外连接,需要用到联合union
-- union:合并并去重,union all:只合并
-- 相当于把左外连接和右外连接连起来
select * from category c left join products p on c.cid=p.cid 
union
select * from products p right join category c on c.cid=p.cid;
4.5 内连接和外连接总结

  • 内连接: inner join: 只获取两张表中 交集部分的数据。

  • 左外连接: left join: 以左表为基准, 查询左表的所有数据, 以及与右表有交集的部分 。

  • 右外连接: right join: 以右表为基准, 查询右表的所有的数据, 以及与左表有交集的部分。

综合练习:

-- 创建数据库
create database lsh01;
-- 切换数据库
use lsh01;
-- 创建表
create table emp(
	empno int primary key auto_increment
	comment '员工编号',
	ename varchar(10) comment '员工姓名',
	mgr int comment '领导编号',
	hiredate date comment '入职日期',
	sal double(7,2) comment '月薪',
	comm double(7,2) comment '奖金率',
	deptno int comment '部门编号'
);

-- 插入数据
INSERT INTO emp VALUES (1, '曹操', 0, '2021-12-28', 30000.00, 0.30, 3);
INSERT INTO emp VALUES (2, '吕布', 1, '2021-12-29', 20000.00, 0.20, 3);
INSERT INTO emp VALUES (3, '刘备', 0, '2022-01-12', 35000.00, 0.40, 3);
INSERT INTO emp VALUES (4, '关羽', 3, '2021-12-31', 16000.00, 0.20, 1);
INSERT INTO emp VALUES (5, '赵云', 3, '2021-08-09', 12000.00, 0.20, 2);
INSERT INTO emp VALUES (6, '张飞', 3, '2022-01-01', 8000.00, 0.20, 4);
INSERT INTO emp VALUES (7, '宋江', 0, '2021-12-31', 25000.00, 0.30, 3);
INSERT INTO emp VALUES (8, '李逵', 7, '2021-09-17', 8000.00, 0.20, 4);
INSERT INTO emp VALUES (9, '林冲', 7, '2022-01-22', 3000.00, 0.10, 4);
INSERT INTO emp VALUES (10, '孙二娘', 9, '2022-01-06', 7000.00, 0.20, 1);
INSERT INTO emp VALUES (11, '吴用', 7, '2021-12-27', 2500.00, 0.10, 2);
INSERT INTO emp VALUES (12, '贾宝玉', 0, '2021-12-30', 12000.00, 0.30, 3);
INSERT INTO emp VALUES (13, '元春', 12, '2022-01-01', 9000.00, 0.20, 4);
INSERT INTO emp VALUES (14, '惜春', 12, '2021-08-12', 3000.00, 0.10, NULL);
INSERT INTO emp VALUES (15, '迎春', 12, '2021-12-28', 4200.00, 0.10, 2);
INSERT INTO emp VALUES (16, '探春', 12, '2022-01-12', 9600.00, NULL, 1);
INSERT INTO emp VALUES (17, '王熙凤', 0, '2021-08-11', 18000.00, 0.20, 3);
INSERT INTO emp VALUES (18, '秦可卿', 0, '2022-01-20', 16000.00, NULL, 3);

create table dept(
	deptno int primary key auto_increment comment '部门编号',
	dname varchar(10) comment '部门名称'
);

-- 插入数据
INSERT INTO dept VALUES (1, '开发部');
INSERT INTO dept VALUES (2, '人事部');
INSERT INTO dept VALUES (3, '行政部');
INSERT INTO dept VALUES (4, '财务部');
INSERT INTO dept VALUES (5, '公关部');

create table salgrade(
	grade char(1) primary key comment '等级',
	losal double comment '此等级的最低工资',
	hisal double comment '此等级的最高工资'
);

-- 插入数据
INSERT INTO salgrade VALUES ('A', 0, 3000);
INSERT INTO salgrade VALUES ('B', 3001, 8000);
INSERT INTO salgrade VALUES ('C', 8001, 10000);
INSERT INTO salgrade VALUES ('D', 10001, 50000);
-- 1. 查询没有奖金,且工资小于15000的员工的姓名
select ename from emp where sal<15000 and comm is null;
select ename from emp where comm is null and sal < 15000;

-- 2. 查询员工表中部门编号不是3或者工资是12000的员工信息
select * from emp where deptno != 3 or sal=12000;
select ename from emp where deptno != 3 or sal = 12000;

-- 3. 查看员工表中涉及到了哪些部门
-- 就是总共有那些部门,要用到分组
select deptno from emp group by deptno order by deptno;
select distinct deptno from emp;-- 直接去重

-- 4. 查询员工的姓名和部门号和年薪,按年薪降序,按部门号升序
-- 还要加上奖金
select ename,deptno,(1+ifnull(comm,0))*sal*12 from emp order by (1+ifnull(comm,0))*sal*12 desc,deptno;
select ename,deptno,(1+ifnull(comm,0))*sal*12 年薪 from emp order by 年薪 desc, deptno asc;

-- 5. 选择工资不在8000到17000的员工的姓名和工资,按工资降序
select ename,sal from emp where sal<8000 or sal>17000 order by sal desc;
select ename, sal from emp where sal not between 8000 and 17000 order by sal desc;

-- 6. 查询名字中包含春的员工信息,按照工资降序
-- 模糊查询(包含春'%春%')
select * from emp where ename like '%春%' order by sal desc;

-- 7. 查询公司员工工资的最大值、最小值、平均值(保留两位小数)、总和
-- 分组函数,一般与having一起使用,having分组后的过滤
-- round(2.1456,2)保留2位小数,四舍五入
select max(sal),min(sal),round(avg(sal),2),sum(sal) from emp;
select max(sal),min(sal),round(avg(sal),2),sum(sal) from emp;

-- 8. 查询部门编号为3的员工个数
-- 个数count(1)或者count(字段)
select count(1) from emp where deptno=3;
select count(*) from emp where deptno=3;

-- 9. 查询各部门的员工工资的最大值、最小值、平均值、总和,并按部门编号升序
select max(sal),min(sal),avg(sal),sum(sal),deptno from emp group by deptno order by deptno;
select deptno,max(sal),min(sal),avg(sal),sum(sal) from emp group by deptno order by deptno asc;

-- 10. 查询各个领导手下员工的最低工资,其中最低工资不能低于6000的
-- 自连接
-- SQL92
select min(e1.sal),e2.ename from emp e1,emp e2 where e1.mgr = e2.empno group by e2.ename 
having min(e1.sal)>=6000;
-- 答案不严谨
select mgr,min(sal) from emp group by mgr having min(sal)>=6000;

-- 11. 查询每个部门的编号,员工数量和工资平均值,并按平均工资降序
-- 分组
select deptno,count(1),avg(sal) from emp group by deptno order by avg(sal) desc;
select deptno,count(*),avg(sal) from emp group by deptno order by avg(sal) desc;

-- 12. 查询哪些部门的员工个数大于2,并按照个数降序排序
select count(1),deptno from emp group by deptno having count(1)> 2 order by count(1) desc;
select deptno, count(*) from emp group by deptno having count(*)>2 order by count(*) desc;

-- 13. 查询每个部门有奖金的员工的最高工资>12000的部门编号和最高工资
select deptno,max(sal) from emp where comm is not null group by deptno having max(sal)>12000;
select deptno,max(sal) from emp where comm is not null group by deptno having max(sal)>12000;

-- 14. 查询领导编号 > 3 的每个领导手下的员工最低工资 > 5000的领导编号是哪些,以及其手下的最低工资
select mgr,min(sal) from emp where mgr>3 group by mgr having min(sal)>5000;
select mgr,min(sal) from emp where mgr>3 group by mgr having min(sal)>5000;

-- 15. 查询员工名和对应的部门名
-- 多表
-- SQL92
select ename,dname from emp e,dept d where e.deptno=d.deptno;
-- SQL99
select ename,dname from emp e join dept d on e.deptno=d.deptno;
select ename,dname from emp e,dept d where e.deptno=d.deptno;

-- 16. 查询有奖金的员工名、部门名及奖金率
select ename,dname,comm from emp e,dept d where e.deptno=d.deptno and comm is not null;
select ename,dname,comm from emp e, dept d where e.deptno=d.deptno and comm is not null;

-- 17. 查询姓名中包含'春'的员工的名字、上级的名字
-- 模糊查询,自连接
-- SQL92
select e1.ename,e2.ename from emp e1,emp e2 where e1.mgr=e2.empno and e1.ename like '%春%';
select e.ename, m.ename from emp e, emp m where e.mgr=m.empno and e.ename like '%春%';

-- 18. 查询没有部门的员工信息及部门信息
select * from emp e left join dept d on e.deptno=d.deptno where e.deptno is null;
select * from emp e left join dept d on e.deptno=d.deptno where e.deptno is null;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值