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 d
join (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, -- 地区ID
title 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 a1
join 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:查询每个月的销售额和前一个月的销售额的差值
-- 查询结果字段:
-- 月份、当前月销售额、前一个月销售额,当前月销售额和前一个月销售额的差
SELECT
A.month, -- 月份
A.revenue, -- 当月销售额
B.revenue, -- 上个月销售额
-- 计算当前销售额和上个月销售额的差值
A.revenue - B.revenue AS `difference`
FROM sales A
JOIN sales B
ON A.month=B.month+1;
-- 示例2:查询截止到每个月的销售额。
-- 查询结果字段:
-- 月份,截止当前的累计销售额
SELECT
A.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) rn
from employee) t1
where 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;
-- 显示每个人的工资和平均工资
select
ename,
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) nth
from employee;
-- ntile(5) 把窗口内的数据分为5个等份,不同的等份之间差值为1
select *,
ntile(5) over () nth
from 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月销量,依次类推
SELECT
month,
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月销量,依次类推
SELECT
month,
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;-- 英语