数据库的高阶知识

目录

  • [一、case when](#一、case when)
  • 二、几种常见的嵌套查询
    • [2.1 比较运算符](#2.1 比较运算符)
    • [2.2 ANY/ALL 关键词](#2.2 ANY/ALL 关键词)
    • [2.3 in 关键词](#2.3 in 关键词)
    • [2.4 EXISTS关键词](#2.4 EXISTS关键词)
    • [2.5 in和exists的异同点](#2.5 in和exists的异同点)
  • 三、开窗函数

数据库的基本知识
数据库的高阶知识

一、case when

在实际工作中,经常会涉及以下两类问题:

  • 数据的映射处理:比如将离散的数字变成有意义的业务说明,或是将连续的数值映射到离散的区间带
  • 有针对性的筛选计算:将细颗粒单位转换为粗颗粒单位的聚合运算

这两类问题,我们通常使用case when语句来解决,case语句其实就是mysql中的控制流语句,类似于其他很多编程工具中的if ...then...的分支判断逻辑。

关于case when的表达方式有两种:"简单case函数法"和"case搜索函数法"。这两种方法各有优点,比如"简单case函数法"的语法更加简洁,但是只能处理等式的问题,"case搜索函数法"更加灵活好用,可以处理等式问题也可以处理不等式问题。

sql 复制代码
# 简单case函数法
case <name>
	when <when_condition> then <result>
	[when <when_condition> then <result>]
	[......]
	[else <else_result>]
end
#case搜索函数法
case 
	when <bool_condition> then <result>
	[when <bool_condition> then <result>]
	[.......]
	[else <else_result>]
end

下面以电商数据为例学习CASE WHEN语法在数据查询中几种常见用法,该数据一共包含7个字段和1000条样本,7个字段分别是用户ID,用户出生日期、下单时间、订单ID、支付方式、支付金额和是否享受折扣。数据链接:https://pan.quark.cn/s/af980c28a2d0

sql 复制代码
-- 离散数值与实际业务含义一一对应
select *,
		case 
		when is_discount = 1 then "享受折扣"
		else "无折扣"
		end discount_new,
		case
		when pay_type = 1 then "支付宝支付"
		when pay_type = 2 then "微信支付"
		else "银行卡支付"
		end pay_type_new
from goods_orders


#根据用户的出生日期将用户分为70后,80后,90后,00后
select *,
		case
		when year(birthday) between 1970 and 1979 then '70后'
		when year(birthday) between 1980 and 1989 then '80后'
		when year(birthday) between 1990 and 1999 then '90后'
		else '00后'
		end AGE_GROUP
from goods_orders

#长形统计表转换成宽形统计表
select month(order_date) as imonth,
				sum(case when pay_type = 1 then pay_amt end) as 'ali_pay',
				sum(case when pay_type = 2 then pay_amt end) as 'wechat',
				sum(case when pay_type = 3 then pay_amt end) as 'bank_card'
from goods_orders		
where year(order_date) = 2023
group by imonth
order by imonth

二、几种常见的嵌套查询

嵌套查询,也称为子查询,是实际工作中经常用到的一种查询方式。子查询其实就是在已有的查询语句中的where后面再嵌套一层查询语句,也就是把内层查询结果当做外层查询参照的数据表来使用。

在工作中,经常会遇见4种子查询,即含有比较运算符 (>、>=、<、<=、=、!=)、ANY/ALL关键词IN关键词 以及EXISTS关键词 的嵌套查询。数据链接https://pan.quark.cn/s/223ade8607a3

2.1 比较运算符

比较运算符用于比较两个值,返回布尔值(TRUE 或 FALSE)。

sql 复制代码
#创建学生信息表
CREATE TABLE stu_info (
    id INT AUTO_INCREMENT PRIMARY KEY, -- 学生ID,自增主键
    iname VARCHAR(20),                 -- 学生姓名
    gender CHAR(1),                    -- 性别(M: 男, F: 女)
    department VARCHAR(10),            -- 所属院系
    age INT,                           -- 年龄
    province VARCHAR(10),              -- 省份
    email VARCHAR(50),                 -- 邮箱
    mobilephone CHAR(11)               -- 手机号(11位)
);

INSERT INTO stu_info (iname, gender, department, age, province, email, mobilephone)
VALUES
('张三', 'M', '计算机', 20, '北京', '[email protected]', '13800138000'),
('李四', 'M', '数学', 21, '上海', '[email protected]', '13800138001'),
('王五', 'F', '物理', 22, '广东', '[email protected]', '13800138002'),
('赵六', 'M', '化学', 19, '江苏', '[email protected]', '13800138003'),
('孙七', 'F', '生物', 20, '浙江', '[email protected]', '13800138004'),
('周八', 'M', '计算机', 21, '北京', '[email protected]', '13800138005'),
......
('武勇', 'M', '生物', 21, '浙江', '[email protected]', '13800138049');

#学生成绩表
CREATE TABLE stu_score (
    id INT,          -- 学生ID
    Chinese TINYINT, -- 语文成绩(0-100)
    Math TINYINT,    -- 数学成绩(0-100)
    English TINYINT  -- 英语成绩(0-100)
);

INSERT INTO stu_score (id, Chinese, Math, English)
VALUES
(1, 87, 90, 83),
(2, 78, 85, 88),
(3, 92, 76, 81),
......
(50, 87, 80, 83);

# 1.查询年龄超过所有学员平均年龄的学员信息
#第一步先计算学员平均年龄
select avg(age) from stu_info

#筛选超过平均年龄的学员信息
select * from stu_info
where age >= 20.5

#子查询
select * from stu_info
where age >= (select avg(age) from stu_info)

# 2.查询年龄不低于所属系平均年龄的学员信息
select * from stu_info as t1
where age >=(
select avg(age) from stu_info as t2
where t1.department = t2.department)

2.2 ANY/ALL 关键词

对于含比较运算符的嵌套查询来说,嵌套部分的查询语句只能返回一个值 。那如果子查询返回多个值,就需要用到ANY或者ALL关键词了。通常,ANY/ALL关键词经常和比较运算符连用,下面是6种比较运算符与ANY/ALL 关键词的搭配结果:

组合 含义 组合 含义
>ANY 大于子查询结果中的某个值 >ALL 大于子查询结果中的所有值
>=ANY 大于等于子查询结果中的某个值 >=ALL 大于等于子查询结果中的所有值
<ANY 小于子查询结果中的某个值 <ALL 小于子查询结果中的所有值
<=ANY 小于等于子查询结果中的某个值 <=ALL 小于等于子查询结果中的所有值
=ANY 等于子查询结果中的某个值 =ALL 等于子查询结果中的所有值
!=ANY 不等于子查询结果中的某个值 !=ALL 不等于子查询结果中的所有值
sql 复制代码
# 2.查询非物理系中比物理系任意一个学员年龄小的学员信息
select * from stu_info 
where department != '物理' and
age < any(
select distinct age 
from stu_info
where department = '物理'
)

#3.查询非物理系中比物理系所有学员年龄大的学员信息
select * from stu_info 
where department != '物理' and
age > all(
select distinct age 
from stu_info
where department = '物理'
)

2.3 in 关键词

IN 用于判断某个值是否在指定的值列表中。它可以替代多个 OR 条件,使查询更简洁。

sql 复制代码
# 查询计算机和物理系的学员信息
select * from stu_info
where department in ("计算机","物理")

#查询和张三、王五同一个系的学员信息
select * from stu_info
where department in (
select department from stu_info
where iname in("张三","王五"))

#查询语文成绩大于85分的学员信息
select * from stu_info where id in (select id from stu_score
where Chinese >85)

2.4 EXISTS关键词

EXISTS 用于检查子查询是否返回任何行。如果子查询返回至少一行,则 EXISTS 返回 TRUE,否则返回 FALSE。

sql 复制代码
#查询语文成绩大于85分的学员信息
select * from stu_info where exists (select * from stu_score
where stu_score.id = stu_info.id and Chinese >85)

2.5 in和exists的异同点

  • 使用场景对比
      in适合用于静态值列表或子查询结果集较小的情况。
sql 复制代码
-- 查询数学成绩为 85、90 或 95 的学生
SELECT * FROM stu_score
WHERE Math IN (85, 90, 95);

-- 查询在某个班级的学生
SELECT * FROM stu_info
WHERE class_id IN (SELECT class_id FROM class WHERE class_name = '计算机');

exists适合用于动态子查询或子查询结果集较大的情况。

sql 复制代码
-- 查询有成绩记录的学生
SELECT * FROM stu_info
WHERE EXISTS (
    SELECT 1 FROM stu_score
    WHERE stu_score.id = stu_info.id
);

-- 查询没有成绩记录的学生
SELECT * FROM stu_info
WHERE NOT EXISTS (
    SELECT 1 FROM stu_score
    WHERE stu_score.id = stu_info.id
);
  • 性能对比

    IN 的性能:

    当子查询结果集较小时,IN 的性能较好,因为子查询的结果会被缓存,外部查询只需要与缓存的结果进行比较。

    当子查询结果集较大时,IN 的性能较差,因为缓存大量数据会占用内存,且比较操作较慢。

    EXISTS 的性能:

    当子查询结果集较大时,EXISTS 的性能较好,因为它不需要缓存结果,且可以在找到第一个匹配项时立即返回 TRUE。

    当子查询结果集较小时,EXISTS 的性能可能略低于 IN,因为需要为外部查询的每一行执行一次子查询。

  • NULL值处理区别

    in:如果子查询的结果中包含 NULL,IN 会忽略 NULL,不会将其与外部查询的值进行比较。

sql 复制代码
SELECT * FROM stu_score
WHERE Math IN (85, 90, NULL);
#如果 Math 是 NULL,上述查询不会返回该行。

EXISTS:不关心子查询的具体值,只关心是否存在匹配的行。

sql 复制代码
SELECT * FROM stu_info
WHERE EXISTS (
    SELECT 1 FROM stu_score
    WHERE stu_score.id = stu_info.id AND Math IS NULL
);

三、开窗函数

结合order by关键词和limit关键词是可以解决很多的topN问题,比如从二手房数据集中查询出某个地区的最贵的10套房,从电商交易数据集中查询出实付金额最高的5笔交易,从学员信息表中查询出年龄最小的3个学员等。但是,如果需求变成从二手房数据集中查询出各个地区最贵的10套房,从电商数据集中查询出每月实付金额最高的5笔交易,从学员信息表中查询出各个科系下年龄最小的3个学员,该如何解决呢?

其实这类问题的核心就是,筛选出组内的topN,而不是从全部数据集中挑选出topN。遇到这种既需要分组也需要排序的问题,直接上开窗函数就能解决了。

(1)开窗函数定义

开窗函数是 SQL 中一种特殊的函数,用于在查询结果集的"窗口"(即一组相关行)上执行计算。与普通的聚合函数(如 SUM、AVG)不同,开窗函数不会将多行合并为一行,而是为每一行返回一个计算结果,同时保留原始行的详细信息。 注:MySQL 8.0 及以上版本支持开窗函数,低于版本不支持。

开窗函数分为两部分,一部分是函数名称,开窗函数的数量比较少,总共才11个开窗函数+聚合函数(所有聚合函数都可以用作开窗函数),根据函数的性质,有的需要写参数,有的不需要写参数。

另一部分为over语句,over()语句必须要写的,里面的参数都是非必须参数,可以根据需求有选择的使用。

sql 复制代码
函数名(字段) OVER (
    [PARTITION BY 分组字段] #含义是根据此字段将数据集分为多份
    [ORDER BY 排序字段] #每个窗口的数据依据此字段进行升序或降序排列
    [ROWS/RANGE 窗口范围]
)

函数名:开窗函数的名称,如 ROW_NUMBER()、RANK()、SUM() 等。
OVER:定义窗口的范围和规则。
PARTITION BY:将数据分组,类似于 GROUP BY,但不会合并行。
ORDER BY:指定窗口内的排序规则。
ROWS/RANGE:定义窗口的物理或逻辑范围。
sql 复制代码
#创建虚拟的业务员销售数据
create table Sales(
						idate date,
						iname char(5),
						sales int)
#向表中插入数据
insert into Sales values('2024-01-01', '张三', 200)


-- 插入50行模拟数据
INSERT INTO Sales (idate, iname, sales)
VALUES
('2024-01-01', '张三', 200),
('2024-01-02', '张三', 650),
('2024-01-03', '王五', 200),
('2024-01-04', '赵六', 250),
('2024-01-05', '王五', 300),
......
('2024-02-18', '苏勇', 2500);

# 一、查询各月中销售业绩最差的业务员
select * from(
select month(idate),iname,sales,row_number() over(partition by month(idate) order by sales) as sales_order from sales) as t  #易错点:在 SQL 中,所有的派生表必须有一个别名,没有指定别名,MySQL 报错
where sales_order= 1

# row_number()、rank()、dense_rank()的区别
select * from(
select month(idate),iname,sales,row_number() over(partition by month(idate) order by sales) as row_order,
								rank() over(partition by month(idate) order by sales) as rank_order,
								dense_rank() over(partition by month(idate) order by sales) as dense_order from sales) as t

# 连续登录的问题:创建虚拟的用户登录表
create table user_login(
				user_id varchar(100),
				login_time datetime);
# 二、查看每位用户连续登录的情况
#1、时间戳格式修改为时间格式
create table user_login_date (
select distinct user_id,date(login_time) login_date
from user_login)

# login_date - irank 相等表示连续登录
-- select * from user_login_date

#2、对用户登录数据进行排序
create table user_login_data_1 (
			select *,rank() over(partition by user_id order by login_date) irank
			from user_login_date)

-- select * from user_login_data_1

# 3、date_sub 从指定的日期减去指定的时间间隔
create table user_login_data_2(
select *,date_sub(login_date,interval irank day) idate from user_login_data_1);

-- select * from user_login_data_2
# 4、计算连续登录的用户情况

select user_id,min(login_date) as start_date,
							max(login_date) as end_date,
							count(login_date) as days
from user_login_data_2
group by user_id,idate


#整合代码
select user_id,min(login_date) as start_date,
							max(login_date) as end_date,
							count(login_date) as days
from (select *,date_sub(login_date,interval irank day) idate from (select *,rank() over(partition by user_id order by login_date) irank
			from (select distinct user_id,date(login_time) login_date
from user_login) as c) as b) as a 
group by user_id,idate


# 三、查看每位用户最大连续登录的天数
select user_id,max(days) 
     from(select user_id,min(login_date) as start_date,
							max(login_date) as end_date,
							count(login_date) as days
from (select *,date_sub(login_date,interval irank day) idate from (select *,rank() over(partition by user_id order by login_date) irank
			from (select distinct user_id,date(login_time) login_date
from user_login) as c) as b) as a 
group by user_id,idate) as d
group by user_id

# 四、查看在这段时间内连续登录天数>=5天的用户
#方案1:
select distinct user_id from (select user_id,min(login_date) as start_date,
							max(login_date) as end_date,
							count(login_date) as days
from (select *,date_sub(login_date,interval irank day) idate from (select *,rank() over(partition by user_id order by login_date) irank
			from (select distinct user_id,date(login_time) login_date
from user_login) as c) as b) as a 
group by user_id,idate
having days >=5) d 

#方案2:
#lead()函数:静态窗口函数
select *,lead(login_date,4) over (partition by user_id order by login_date ) as idate5
from user_login_date

#计算第5次登录与当天的差值
select *,datediff(idate5,login_date)+1 days
from (select *,lead(login_date,4) over (partition by user_id order by login_date ) as idate5
from user_login_date) as a 

#查找相差天数为5的记录
select distinct user_id from (select *,datediff(idate5,login_date)+1 days
from (select *,lead(login_date,4) over (partition by user_id order by login_date ) as idate5
from user_login_date) as a )as b 
where days = 5

DATE_SUB 是 SQL 中用于从日期中减去指定时间间隔的函数。它通常用于计算过去某个时间点的日期。以下是 DATE_SUB 函数的详细用法及解释:

sql 复制代码
DATE_SUB(date, INTERVAL value unit)
# date:要操作的日期或日期时间值。
# INTERVAL:表示时间间隔的关键字。
# value:要减去的时间间隔值,必须是一个整数。
# unit:时间间隔的单位,支持以下值:秒、分钟、小时、天、周、月、季度、年
示例:
SELECT DATE_SUB('2023-10-01', INTERVAL 5 DAY); # 从 2023-10-01 减去 5 天:结果:2023-09-26

LEAD() 是 SQL 中的一种 窗口函数(Window Function),用于获取当前行之后的某一行数据。它通常用于分析时间序列数据或计算相邻行之间的差异。

sql 复制代码
LEAD(column_name, offset, default_value) OVER (
    [PARTITION BY partition_expression]
    [ORDER BY sort_expression]
)
# column_name:需要获取值的列名。
# offset(可选):指定要获取的行数偏移量。默认值为 1,表示获取下一行。
# default_value(可选):如果偏移量超出范围(如最后一行没有下一行),则返回的默认值。如果未指定,默认返回 NULL。
# PARTITION BY(可选):将数据分组,LEAD() 函数会在每个分组内单独计算。
# ORDER BY(可选):指定窗口内的排序规则。

LEAD() 函数的作用:获取当前行之后的某一行数据;常用于计算相邻行之间的差异,或分析时间序列数据。

相关推荐
Q_Boom几秒前
MySQL中的回表是什么?
数据库·mysql·面试
二月十六2 分钟前
SQL Server性能分析利器:SET STATISTICS TIME ON 详解与实战案例
数据库·性能分析
akbar&1 小时前
计算机四级 - 数据库原理 - 第8章「分布式、对象-关系、NOSQL数据库」
数据库·笔记
多多*1 小时前
牛客周赛84 题解 Java ABCDEFG AK实录
数据库·windows·macos·github·objective-c·mybatis·cocoa
椅子哥2 小时前
MyBatis操纵数据库-XML实现(补充)
xml·java·数据库·spring·mybatis·springboot
!chen2 小时前
查询修改ORACLE的server、客户端和导出dmp文件 字符集编码
数据库·oracle
是懒羊羊吖~2 小时前
【sql靶场】第15、16关-post提交盲注保姆级教程
数据库·笔记·sql·时间盲注·布尔盲注·sql靶场
Pandaconda4 小时前
【后端开发面试题】每日 3 题(十二)
数据库·后端·面试·负载均衡·高并发·后端开发·acid
zhouwhui5 小时前
【openGauss】物理备份恢复
数据库·gaussdb
神洛华5 小时前
PowerBI数据建模基础操作1:数据关系(基数、双向筛选、常规关系、有限关系)与星型架构(维度表、事实表)
数据库·powerbi