MySql复习知识及扩展内容
DDL操作库和表
-- todo ------------操作库----------------------------------
-- 创建库
create database if not exists day10 charset utf8;
create database if not exists day11 char set gbk;
-- 使用库
use day10;
-- 查询库
show databases ;
-- 删除库
drop database day11;
-- todo ------------操作表----------------------------------
-- 创建表-- 知道表结构:分析业务得到:设计一个存储学生信息的表.
/*学号 --(列) (字段)姓名年龄手机号性别籍贯生日
*/
create table student (id int primary key auto_increment,name varchar(20),age int,phone varchar(20),gender varchar(10),address varchar(20),birthday date
);
-- 查询表
show tables ;
-- 查询表的字段,类型,约束
desc student ;
-- 查询建表语句
show create table student;
-- 修改表
-- 修改表的列--添加列 -- 添加入学日期
alter table student add join_date date ;
-- 修改表的列--删除列
alter table student drop join_date;
-- 修改表的列--修改列的类型
alter table student modify name varchar(10);
-- 修改表的列--修改列的约束 -- name不能为空
alter table student modify name varchar(10) not null;
-- 修改表的列--修改列名 gender -sex
alter table student change gender sex varchar(10);
-- 修改表名 student--> stu
rename table student to stu;
-- 删除表
drop table stu;
DML操作数据
-- todo ------------操作数据 增删改----------------------------------
-- 添加一条数要求全列值插入.
insert into stu values (1,'tom',19,'17799889988','男','北京','1999-09-09');
-- 有主键自增的情况下可以主键可以给null值.实际上存储的是自增长的值.
insert into stu values (null,'jerry',18,'17799889966','男','北京','1999-09-10');
-- 添加一条数据.要求 只插入前3列
insert into stu (id, name, age) values (null,'rose',18);
-- 一次添加2条数据到表中.
insert into stu values (null,'张三',18,'17799889911','男','北京','1999-09-10'),(null,'李四',18,'17799889922','男','北京','1999-09-10');
-- ------------------------------------------------------------------
-- rose性别改为女
update stu set sex = '女' where name = 'rose';
-- 删除rose这个记录
delete from stu where name = 'rose';
-- 修改所有人的地址是上海
update stu set address = '上海';
DQL单表查询
create table product(pid int primary key auto_increment,pname varchar(20),price double,category_id varchar(20)
);
insert into product values
(1, '联想', 5000, 'c001'),
(2, '海尔', 3000, 'c001'),
(3, '雷神', 5000, 'c001'),
(4,'杰克琼斯',800, 'c002'),
(5, '真维斯', 200,'c002'),
(6,'花花公子',440,'c002'),
(7,'劲霸',2000,'c002'),
(8,'香奈儿',800,'c003'),
(9,'相宜本草',200,'c003'),
(10,'面霸',5,'c003'),
(11,'好想你枣',56,'c004'),
(12,'香飘飘奶茶',1,'c005'),
(13,'海澜之家',1000,'c002');
基本查询
#### 基本查询
select * from product;-- 查询所有记录的所有列.
select pid,pname,price from product;-- 指定列查询
select distinct price, pname from product;-- 两个列联合去重
select distinct category_id from product; -- 查询商品不同的分类
条件查询
#### 条件查询 > < >= <= != <>
select * from product where pid > 3;
-- 查询分类为c001的商品
select * from product where category_id = 'c001';
范围查询
#### 范围查询
-- 连续的范围 -- 查询价格在500-800之间的商品
select * from product where price between 500 and 800;
select * from product where price >=500 and price <= 800;
-- 非连续的范围 --查询pid是c001和c003商品
select * from product where category_id in ('c001','c003');
select * from product where category_id= 'c001' or category_id= 'c003';
模糊查询
#### 模糊查询
/**列 like 符号 (% 和 _)%:表示0或者多个任意字符_:表示一个任意字符*/
-- 需求:查询商品名字包含香的.
select * from product where pname like '%香%';
-- 需求:查询商品名字以香开头的商品.
select * from product where pname like '香%';
-- 需求:查询商品名字以香开头的并且名字是3个字的.
select * from product where pname like '香__';
非空查询
#### 非空查询
-- 查询价格不是空的商品
select * from product where price is not null;
-- 查询价格是空的商品
select * from product where price is null;
select * from product where price = null;
排序查询
#### 排序查询
-- 按照价格升序排序
select * from product order by price;
select * from product order by price asc;-- 默认是asc
-- 按照价格降序排序
select * from product order by price desc;
-- 多字段排序 todo 第一排序字段内容一致此时第二排序字段才会生效.
-- 先按照价格降序排序.在按照id降序排序
select * from product order by price desc , pid desc ;
聚合查询
#### 聚合函数查询 max,min,avg,sum,count
select max(price) from product;
select min(price) from product;
select max(price),min(price),avg(price) from product;
select avg(price) from product where pid >= 10;-- todo avg遇到null值会忽略该条记录.参数平均值的人就会少一个.
# sum 是求和 对单列的值进行累加
select sum(price) from product where pid >= 10;-- sum遇到null值会当做0来计算.
#count 是求记录数.(统计有几条记录)
select count(price) from product where pid >= 10;-- todo count遇到null会忽略该条数据.
select count(*) from product where pid >= 10;-- 会参考所有的列.只要有一个列不是null 就会统计.
select count(pid) from product where pid >= 10;-- 主键不会为null
select count(1) from product where pid >= 10;-- 有一条记录无论列是什么值都会统计该条记录.
分页查询
#### 分页查询 limit [m],n (m起始点[默认0],n是个数)
-- 查询第一页的数据.每页展示5条.
select * from product limit 0,5;
-- 查询前3条记录
select * from product limit 3;-- 省略了m值.默认是0
-- 查询第3页的数据.每页展示5条. m的值需要计算 > 公式: (当前页码-1) * 每页展示个数 ==> (3-1) * 5 = 10
select * from product limit 10,5;
分组查询
#### 分组查询 group by [having 过滤条件]
/**分组思想:1.确定要分组的列.(按照什么来分) (一般不要按照主键分组)2.分组后你想干什么...2.1聚合统计计算 (聚合函数)2.2去重查询*/
-- 不同价格的商品有几个
select price, count(1) from product group by price;
-- 不同分类的商品中那个商品最贵.
select category_id,max(price) from product group by category_id;
-- 查询有几个分类
select distinct category_id from product; -- distinct去重
select category_id from product group by category_id; -- group by 去重
-- -- 不同价格的商品个数大于1的有哪些?
select price, count(1) ct from product group by price having ct > 1;
# select price, count(1) ct from product where ct > 1 group by price ; -- where 只能对表中的列进行过滤.
DQL多表关联查询
部门表: 部门编号,部门名称,部门位置 员工表: 员工编号,员工姓名,员工年龄,所属部门
-- todo ------------查询数据 多表查询----------------------------------
# 部门表: 部门编号,部门名称,部门位置
create table dept (id int primary key auto_increment,dname varchar(20),addr varchar(30)
);
# 员工表: 员工编号,员工姓名,员工年龄,所属部门
create table emp(id int primary key auto_increment,ename varchar(20),age int,dept_id int,foreign key (dept_id) references dept(id)
);
-- 插入数据;
insert into dept values (null,'财务部','上海'),(null,'人事部','北京'),(null,'研发部','北京');
insert into emp values (null,'张三',25,1),(null,'李四',25,2),(null,'王五',25,2),(null,'赵六',25,null);
交叉连接(笛卡尔积)
两个表相乘.
##### 交叉连接(笛卡尔积)
select * from dept cross join emp;
select * from dept , emp;
内连接
##### 内连接 -- 查询的是两个表的交集.
select * from dept d inner join emp e on d.id = e.dept_id;
-- 此时没有员工的部门和没有部门的员工都不能查出.
左连接
##### 左连接 -- 左表的全部和左右内俩接的结果
select * from dept d left join emp e on d.id = e.dept_id;
-- 此时员工表中没有部门的员工不能出现.
右连接
##### 右连接 -- 右表的全部和右内俩接的结果
select * from dept d right join emp e on d.id = e.dept_id;
-- 此时部门表中没有员工的部门信息不能出现.
DQL子查询
子查询的结果是1个值(单行单列)
子查询的结果是多个值(多行单列)
子查询的结果是一个表(多行多列)
子查询也叫嵌套查询,把一个大的问题拆分成为多个小的问题,小问题解决了大问题也随之解决.
-- todo 子查询 ----------------------------------
##### 子查询的结果是1个值(单行单列)
-- 查询价格最高的产品名字
select max(price)
from product; -- 5000
select *
from product
where price = (select max(price) from product);
##### 子查询的结果是多个值(多行单列)
-- 查询财务部和人事部的所有员工.
select id
from dept
where dname in ('财务部', '人事部');
select *
from emp
where dept_id in (select id from dept where dname in ('财务部', '人事部'));
##### 子查询的结果是一个表(多行多列)
-- 高于员工平均年龄的员工和部门信息.
select *
from emp
where age > (select avg(age) from emp);
select *
from dept djoin (select * from emp where age > (select avg(age) from emp)) e on d.id = e.dept_id;
DQL 自关联查询
案例1:
-- 创建一个地区表
CREATE TABLE areas(id VARCHAR(30) NOT NULL PRIMARY KEY, -- 地区IDtitle VARCHAR(30), -- 地区名称pid VARCHAR(30) -- 地区的上级地区的ID
);
insert into areas
values (1, '河南省', null),(2, '郑州', 1),(3, '洛阳', 1),(4, '新乡', 1);
-- 示例1:查询'河南省'下的所有市的信息
select a1.title, a2.title
from areas a1join areas a2 on a1.id = a2.pid;
案例2:
-- 【自关联扩展:自关联的妙用】
CREATE TABLE sales (month INT NOT NULL, -- 月份revenue DECIMAL(10, 2) -- 销售额
);
INSERT INTO sales
VALUES(1, 1000),(2, 800),(3, 1200),(4, 2000),(5, 1800),(6, 5000),(7, 3000),(8, 2500),(9, 1600),(10, 2200),(11, 900),(12, 4600);
-- 示例1:查询每个月的销售额和前一个月的销售额的差值
-- 查询结果字段:
-- 月份、当前月销售额、前一个月销售额,当前月销售额和前一个月销售额的差
SELECTA.month, -- 月份A.revenue, -- 当月销售额B.revenue, -- 上个月销售额-- 计算当前销售额和上个月销售额的差值A.revenue - B.revenue AS `difference`
FROM sales A
JOIN sales B
ON A.month=B.month+1;
-- 示例2:查询截止到每个月的销售额。
-- 查询结果字段:
-- 月份,截止当前的累计销售额
SELECTA.month,SUM(B.revenue) AS `revenue`
FROM sales A
JOIN sales B
ON A.month >= B.month
GROUP BY A.month;
DQL窗口函数
create table employee
(dname varchar(20), -- 部门名eid varchar(20),ename varchar(20),hiredate date, -- 入职日期salary double -- 薪资
);
insert into employee
values ('研发部', '1001', '刘备', '2021-11-01', 3000);
insert into employee
values ('研发部', '1002', '关羽', '2021-11-02', 5000);
insert into employee
values ('研发部', '1003', '张飞', '2021-11-03', 7000);
insert into employee
values ('研发部', '1004', '赵云', '2021-11-04', 7000);
insert into employee
values ('研发部', '1005', '马超', '2021-11-05', 4000);
insert into employee
values ('研发部', '1006', '黄忠', '2021-11-06', 4000);
insert into employee
values ('销售部', '1007', '曹操', '2021-11-01', 2000);
insert into employee
values ('销售部', '1008', '许褚', '2021-11-02', 3000);
insert into employee
values ('销售部', '1009', '典韦', '2021-11-03', 5000);
insert into employee
values ('销售部', '1010', '张辽', '2021-11-04', 6000);
insert into employee
values ('销售部', '1011', '徐晃', '2021-11-05', 9000);
insert into employee
values ('销售部', '1012', '曹洪', '2021-11-06', 6000);
序号函数
rank(),row_number(),denserank()
-- 需求: 部门分组后按照薪资排序
select*,rank() over (partition by dname order by salary) rn
from employee;
-- 需求: 求出每个部门薪资排在前三名的员工- 分组求TOPN [重点]
select * from (select*,rank() over (partition by dname order by salary) rnfrom employee) t1where t1.rn <= 3;
聚合函数
sum(),count(),avg(),max(),nin()
#### 聚合函数
# sum(),count(),avg(),max(),nin()
select*,sum(salary) over () c1,count(1) over () c2,avg(salary) over () c3,max(salary) over () c4,min(salary) over () c5
from employee;
select*,sum(salary) over (partition by dname) c1,count(1) over (partition by dname) c2,avg(salary) over (partition by dname) c3,max(salary) over (partition by dname) c4,min(salary) over (partition by dname) c5
from employee;
-- 显示每个人的工资和平均工资
selectename,salary,avg(salary) over () c1
from employee;
前后函数
lag(),lead()
#### 前后函数
# lag(),lead()
select*,lag(hiredate,1,'未找到') over (partition by dname order by hiredate) lg,lead(hiredate,1,'未找到') over (partition by dname order by hiredate) ld
from employee;
# 需求1 : 按照部门分组,列出员工薪资和比他薪资靠前的一名员工的薪资.找不到就显示'已是最高薪资'
select * ,lag(salary,1,'已是最高薪资') over (partition by dname order by salary desc ) lg
from employee;
头尾函数
first_value(),last_value()
#### 头尾函数
# first_value(),last_value()
select *,first_value(hiredate) over (partition by dname ) f1,last_value(hiredate) over (partition by dname ) f2
from employee;
-- 需求1 : 统计各部门全部数据.按照日期排序查询第1个入职和最后1个入职员工的薪资
select *,first_value(salary) over (partition by dname order by hiredate rows between unbounded preceding and unbounded following) f1,last_value(salary) over (partition by dname order by hiredate rows between unbounded preceding and unbounded following) l1
from employee;
其它函数
nth_value(), ntile()
# #### 其它函数
# nth_value(), ntile()
select *,nth_value(salary,3) over (partition by dname) nthfrom employee;
-- ntile(5) 把窗口内的数据分为5个等份,不同的等份之间差值为1
select *,ntile(5) over () nthfrom employee;
-- 需求1 :查询每个部门截止目前薪资排在第二和第三的员工信息
select *,nth_value(salary,2) over (partition by dname order by salary desc rows between unbounded preceding and current row ) nth1,nth_value(salary,3) over (partition by dname order by salary desc rows between unbounded preceding and current row) nth2
from employee;
-- 需求2 :ntile()根据入职日期将每个部门的员工分成3组 .
select *,ntile(3) over (partition by dname order by hiredate) nt
from employee;
-- 需求3 :获取每组的第一份数据.抽样查询
with t1 as ( select *,ntile(3) over (partition by dname order by hiredate) nt
from employee )
select * from t1 where t1.nt = 1;
扩展知识
知识点1:窗口函数-自定义 window frame【掌握】
CREATE TABLE `tb_sales` (`month` int(2) NOT NULL,`sales` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `tb_sales` VALUES (1,10),(2,23),(3,14),(4,5),(5,32),(6,22),(7,52),(8,12),(9,19),(10,36),(11,33),(12,69);
思考题:
现有一张某年度的月销量信息表 tb_sales,数据如下:
如何计算截止到每个月的累计销量?1月:1月销量,2月:1月销量+2月销量,3月:1月销量+2月销量+3月销量,依次类推
分区数据范围和window frame数据范围:
在使用窗口函数处理表中的每行数据时,每行数据关联的数据有两种:
1)每行数据关联的分区数据
-
OVER()中什么都不写时,整张表默认是一个分区
-
OVER(PARTITION BY 列名, ...):整张表按照指定的列被进行了分区
2)每行数据关联的window frame数据
-
每行关联的window frame数据范围 <= 每行关联的分区数据范围
-
window frame数据范围是分区数据范围的子集
目前我们所学的窗口函数中,有些窗口函数作用在分区上,有些函数作用在window frame上:
-
聚合函数(SUM、AVG、COUNT、MAX、MIN)作用于每行关联的window frame数据上
-
排名函数(RANK、DENSE_RANK、ROW_NUMBER)作用于每行关联的分区数据上
自定义 window frame 范围:
自定义 window frame 范围有两种方式:
ROWS
和RANGE
SELECT字段,...,<window function> OVER (PARTITION BY 列名, ...ORDER BY 列名, ...[ROWS|RANGE] BETWEEN 上限 AND 下限)
FROM 表名;
-
PARTITION BY 列名, ...
:按照指定的列,对整张表的数据进行分区 -
ORDER BY 列名, ...
:按照指定的列,对每个分区内的数据进行排序 -
[ROWS|RANGE] BETWEEN 上限 AND 下限
:在排序之后的分区数据内,设置每行关联的window frame数据范围
上限和下限的设置:
-
UNBOUNDED PRECEDING
:对上限无限制 -
PRECEDING
: 当前行之前的 n 行 ( n 表示具体数字如:5PRECEDING
) -
CURRENT ROW
:仅当前行 -
FOLLOWING
:当前行之后的 n 行 ( n 表示具体数字如:5FOLLOWING
) -
UNBOUNDED FOLLOWING
:对下限无限制 -
注意:上限需要在下限之前,比如:
ROWS BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING
是错误的
-- 需求:计算截止到每个月的累计销量。1月:1月销量,2月:1月销量+2月销量,3月:1月销量+2月销量+3月销量,依次类推
SELECTmonth,sales,SUM(sales) OVER(# 按照 month 对每个分区(注:此处就一个分区->整张表)数据进行排序ORDER BY month# 指定每行关联分区的 window frame 范围# UNBOUNDED PRECEDING:上限不限制# CURRENT ROW:当前行ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS `running_total`
FROM tb_sales;
window frame定义的简略写法:
自定义 window frame 的边界时,如果使用了CURRENT ROW
作为上边界或者下边界,可以使用如下简略写法:
-
ROWS UNBOUNDED PRECEDING
等价于BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
-
ROWS n PRECEDING
等价于BETWEEN n PRECEDING AND CURRENT ROW
-
ROWS CURRENT ROW
等价于BETWEEN CURRENT ROW AND CURRENT ROW
-
注意,简略写法不适合
FOLLOWING
的情况
ROWS和RANGE的区别:
ROWS和RANGE关键字,都可以用来自定义 windowframe 范围:
ROWS BETWEEN 上限 AND 下限 RANGE BETWEEN 上限 AND 下限
但两者区别如下:
-
ROWS是根据分区数据排序之后,每一行的 row_number 确定每行关联的 window frame 范围的
CURRENT ROW:仅代表当前行
# 假设某一行数据的 row_number 为5,ROWS自定义window frame如下: ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING # 则这一行关联的window frame是:5-2 <= row_number <= 5+2 的数据
-
RANGE是根据分区数据排序之后,每一行的排序列的值确实每行关联的 window frame 范围的
CURRENT ROW: 代表和当前行排序列的值相同的所有行
# 假设某一行排序列的值为5,RNAGE自定义window frame如下: RANGE BETWEEN 2 PRECEDING AND 2 FOLLOWING # 则这一行关联的window frame是:5-2 <= 排序列的值 <= 5+2 的数据
默认的window frame:
在 OVER 中只要添加了 ORDER BY,在没有写ROWS或RANGE的情况下,会有一个默认的 window frame范围:
-
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
insert into tb_sales values (4,5),(4,10);-- 加入重复的月份
-- 需求:计算截止到每个月的累计销量。1月:1月销量,2月:1月销量+2月销量,3月:1月销量+2月销量+3月销量,依次类推
SELECTmonth,sales,SUM(sales) OVER(# 按照 month 对每个分区(注:此处就一个分区->整张表)数据进行排序ORDER BY month# OVER 中添加了 ORDER BY 之后,默认的 window frame 范围# RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS `running_total`
FROM tb_sales;
知识点2:SQL语句的执行顺序
SQL语句的执行顺序如下:
FROM > JOIN > ON > WHERE > GROUP BY > 聚合函数 > HAVING > 窗口函数 > SELECT > DISTINCT > ORDER BY > LIMIT
1) FROM 2) JOIN 3) ON 4) WHERE 5) GROUP BY 6) 聚合函数 7) HAVING 8) 窗口函数 9) SELECT 10) DISTINCT【去重】 11) ORDER BY 12) LIMIT -------------------------------------------------------------------------
SELECT ⑨[DISTINCT] 字段, ⑩...,聚合函数(字段), ⑥窗口函数 ⑧ FROM 表1 ① JOIN 表2 ② ON 关联条件 ③ WHERE 条件 ④ GROUP BY 分组字段, ... ⑤ HAVING 条件 ⑦ ORDER BY 排序字段, ... ⑪ LIMIT M, N; ⑫
知识点3:行转列
-- 创建 score 学生成绩表
CREATE TABLE score
(`学号` VARCHAR(24),`科目` VARCHAR(24),`成绩` INT
);
-- 给 score 表添加测试表
INSERT INTO score
VALUES ('s001', '语文', 90),('s001', '数学', 100),('s001', '英语', 93),('s002', '语文', 98),('s002', '数学', 99),('s002', '英语', 96);
SELECT * FROM score;
-- 科目 行转列
SELECT 学号,min(CASE when 科目 = '语文' THEN 成绩 END) '语文',min(CASE when 科目 = '数学' THEN 成绩 END) '数学',min(CASE when 科目 = '英语' THEN 成绩 END) '英语'
FROM score GROUP BY 1;
知识点4:列转行
-- 快速建表,并插入数据
CREATE table score2
SELECT 学号,min(CASE when 科目 = '语文' THEN 成绩 END) '语文',min(CASE when 科目 = '数学' THEN 成绩 END) '数学',min(CASE when 科目 = '英语' THEN 成绩 END) '英语'
FROM score GROUP BY 1;
-- 列转行
-- UNION合并:奇技淫巧
SELECT 学号,'语文' as '科目',语文 as '成绩' FROM score2 -- 语文
union
SELECT 学号,'数学' as '科目',数学 as '成绩' FROM score2 -- 数学
union
SELECT 学号,'英语' as '科目',英语 as '成绩' FROM score2;-- 英语