※食用指南:文章内容为SQL之母网页在线练习笔记及答案,适合完全没学过SQL的朋友们快速学习最基础的知识。
练习传送门: SQL之母 - 免费SQL自学网站 by 程序员鱼皮
如果想更为系统的学习,推荐入门学习视频:
本人的笔记专栏:
MySQL干货笔记(近4万字)------PAULEENHUI
http://t.csdnimg.cn/vouUr
目录:
[21、查询进阶-关联查询-inner join](#21、查询进阶-关联查询-inner join)
[22、查询进阶-关联查询-other join](#22、查询进阶-关联查询-other join)
[26、查询进阶-开窗函数-sum over](#26、查询进阶-开窗函数-sum over)
[27、查询进阶-开窗函数-sum over order by](#27、查询进阶-开窗函数-sum over order by)
21、查询进阶-关联查询-inner join
💡 select****a.列名1, a.列名2, b.列名3, b.列名4fromA表 ajoinB表 bona.列名2=b.列名4
INNER JOIN根据两个表之间的关联条件,将满足条件的行组合在一起。
※ INNER JOIN 只返回两个表中满足关联条件的交集部分,即在++两个表中都存在的++匹配行。
|----------|----------|------------|-------------|----------------|
| student表 | id(学号) | name(姓名) | age(年龄) | class_id(班级编号) |
| class表 | id(班级编号) | name(班级名称) | level(班级级别) | / |
❓ 请你编写一个 SQL 查询,根据学生表和班级表之间的班级编号进行匹配,返回学生姓名(student_name)、学生年龄(student_age)、班级编号(class_id)、班级名称(class_name)、班级级别(class_level)。
sql
select s.name student_name,s.age student_age,
s.class_id class_id,c.name class_name,c.level class_level
from student s join class c on s.class_id=c.id
❗ 实际工作场景
sql
--运营数据:查询订单是列出用户姓名和会员等级,用于客服核单和会员营销
select o.订单号, o.实付金额, u.用户姓名, u.会员等级
from 电商订单表 o join 用户信息表 u on o.用户ID = u.用户ID
where o.下单日期 = '2026-06-17';
--游戏运营:查询当月玩家的充值记录
select a.订单号,a.充值金额,a.充值时间,b.玩家昵称,b.VIP等级
from 充值记录表 a join 玩家信息表 b on a.玩家ID = b.玩家ID
where a.充值日期 between '2026-06-01' and '2026-06-30'
order by a.充值金额 desc;
22、查询进阶-关联查询-other join
💡 select****a.列名1, a.列名2, b.列名3, b.列名4fromA表 aleft joinB表 bona.列名2=b.列名4
OUTER JOIN根据指定的关联条件,将两个表中满足条件的行组合在一起,并包含没有匹配的行 。
LEFT OUTER JOIN、RIGHT OUTER JOIN 两种类型,它们分别表示查询左表和右表的所有行(即使没有被匹配),再加上满足条件的交集部分
※ 有些数据库并不支持 RIGHT JOIN 语法,把主表(from 后面的表)和关联表(LEFT JOIN 后面的表)++顺序进行调换++即可!
|----------|----------|------------|-------------|----------------|
| student表 | id(学号) | name(姓名) | age(年龄) | class_id(班级编号) |
| class表 | id(班级编号) | name(班级名称) | level(班级级别) | / |
❓ 请你编写一个 SQL 查询,根据学生表和班级表之间的班级编号进行匹配,返回学生姓名(student_name)、学生年龄(student_age)、班级编号(class_id)、班级名称(class_name)、班级级别(class_level),要求++必须返回所有学生的信息++(即使对应的班级编号不存在)。
sql
select s.name student_name,s.age student_age,s.class_id class_id,
c.name class_name ,c.level class_level
from student s left join class c on s.class_id=c.id
❗ 实际工作场景
sql
--工单系统:查询全量工单,用于排版和工单分配
select a.工单号,a.客户名称,a.创建时间,a.工单类型,b.处理人,b.处理时间,
case when b.工单号 is not null then '已处理'else '待处理' end as 处理状态
from 工单主表 a left join 工单处理记录表 b on a.工单号 = b.工单号
where a.创建时间 between '2026-06-01' and '2026-06-30'
order by a.创建时间 asc;
--财务数据:月结对账
select a.发票号,a.客户名称,a.发票金额,a.开票日期,
b.付款单号,b.付款金额,b.付款日期,
case when b.付款单号 is not null then '已付款' else '未付款' end as 付款状态
from 发票表 a left join 付款记录表 b on a.发票号 = b.发票号
where a.开票日期 between '2026-06-01' and '2026-06-30'
order by a.开票日期 desc, a.发票金额 desc;
23、查询进阶-子查询
💡 select列名1,列名2,列名3fromA表where列名3in**(select列名4fromB表** )
子查询是指在一个查询语句内部 ++嵌套++ 另一个完整的查询语句,++内层查询++被称为子查询。子查询可以用于获取更复杂的查询结果或者用于过滤数据。
当执行包含子查询的查询语句时,数据库引擎会**++首先执行子查询++**,然后将其结果作为条件或数据源来执行外层查询。
例1:所有拿到的奖金超过公司平均奖金金额的员工
子查询:计算平均奖金(5000)
主查询:谁拿到的奖金>5000
例2:请朋友吃饭,让她选择餐厅
子查询:我在APP查哪几家想去
主查询:根据我的推荐决定她去哪家
|----------|----------|------------|---------|-----------|----------------|
| student表 | id(学号) | name(姓名) | age(年龄) | score(分数) | class_id(班级编号) |
| class表 | id(班级编号) | name(班级名称) | / | / | / |
❓ 请你编写一个 SQL 查询,使用子查询的方式来获取存在对应班级的学生的所有数据,返回学生姓名(name)、分数(score)、班级编号(class_id)字段。
sql
select name, score, class_id from student
where class_id in (select distinct id from class);
※ 加 DISTINCT 能让子查询提前去重,执行更快更规范。
❗ 实际工作场景
sql
--物业管理:华南地区业主月缴费数据,重点处理异常
select a.区域,a.城市,
count(distinct a.业主ID) as 总业主数,count(distinct b.业主ID) as 欠费业主数,
round(count(distinct b.业主ID) / count(distinct a.业主ID) * 100, 2) as 欠费率,
sum(b.欠费金额) as 总欠费金额
from 业主信息表 a join 物业费账单表 b on a.业主ID = b.业主ID
and b.缴费状态 = '未缴费' and b.账单月份 = '2026-06'
where a.区域 = '华南地区'
and a.业主ID in (select 业主ID from 物业费账单表 where 缴费状态 = '未缴费'
and 账单月份 = '2026-06'and 欠费金额 > 1000)
group by a.区域, a.城市 order by 总欠费金额 desc;
24、查询进阶-子查询-exists
💡 select列名1,列名2,列名3from****A表 where exists / not exists (select列名4fromB表whereA表.列名3=B表.列名4)
子查询中的一种特殊类型是 "exists" 子查询,用于检查主查询的结果集是否存在满足条件的记录,它返回布尔值(True 或 False),而不返回实际的数据。
not exists,用于查找不满足存在条件的记录
|----------|----------|------------|---------|-----------|----------------|
| student表 | id(学号) | name(姓名) | age(年龄) | score(分数) | class_id(班级编号) |
| class表 | id(班级编号) | name(班级名称) | / | / | / |
❓ 请你编写一个 SQL 查询,使用 exists 子查询的方式来获取 ++不存在对应班级的++ 学生的所有数据,返回学生姓名(name)、年龄(age)、班级编号(class_id)字段。
sql
select name,age,class_id from student
where not exists(select id from class where student.class_id=class.id)
❗ 实际工作场景
sql
--内容管理:查找连续日更30天以上的创作者,用于规则激励奖励
select 作者id, 昵称, 粉丝数 from 创作者表 a
where exists (select 作者id from 内容表 b
where a.作者id = b.作者id and b.发布时间 >= now() - interval 30 day
and b.阅读量 > 1000 group by 作者id
having count(distinct date(b.发布时间)) = 30);
--客诉数据:近3个月有投诉过的VIP客户,重点改善客户满意度
select 客户id, 客户姓名, 会员等级 from 客户表 a
where a.会员等级 = 'VIP'and exists (select * from 投诉表 b
where a.客户id = b.客户id and b.投诉日期 >= now() - interval 3 month);
25、查询进阶-组合查询
💡 select列名1,列名2,列名3fromA表union / union all select列名1,列名2,列名3from****B表)
组合查询是一种将多个 SELECT 查询结果合并在一起的查询操作。
UNION 操作:它用于将两个或多个查询的结果集合并, 并去除重复的行 。即如果两个查询的结果有相同的行,则只保留一行。
UNION ALL 操作:它也用于将两个或多个查询的结果集合并, 但不去除重复的行 。即如果两个查询的结果有相同的行,则全部保留。
|--------------|--------|----------|---------|-----------|----------------|
| student表 | id(学号) | name(姓名) | age(年龄) | score(分数) | class_id(班级编号) |
| student_new表 | id(学号) | name(姓名) | age(年龄) | score(分数) | class_id(班级编号) |
❓ 请编写一条 SQL 语句,获取所有学生表和新学生表的学生姓名(name)、年龄(age)、分数(score)、班级编号(class_id)字段,要求++保留重复++的学生记录。
sql
select name,age,score,class_id from student union all
select name,age,score,class_id from student_new
**※**为什么不直接select*代替列名
这需要列顺序完全一致,如果两个表列顺序不一样或后期加列,结果会锁链或者报错
❗ 实际工作场景
sql
--运营数据:汇总不同平台投放转化数据
select '巨量引擎' as 平台, sum(消耗金额) as 总消耗, sum(转化数) as 总转化
from 巨量引擎_广告日报表 where 日期 = '2026-06-17'
union all
select '腾讯广告' as 平台, sum(消耗金额) as 总消耗, sum(转化数) as 总转化
from 腾讯广告_日报表 where 日期 = '2026-06-17'
union all
select '快手' as 平台, sum(消耗金额) as 总消耗, sum(转化数) as 总转化
from 快手_日报表 where 日期 = '2026-06-17';
--销售数据:合并不同业务系统的报表
select 'ERP' as 系统, 订单号, 客户ID, 订单金额
from ERP_订单表 where 下单日期 = '2026-06-17'
union all
select 'WMS' as 系统, 订单号, 客户ID, 订单金额
from WMS_订单表 where 下单日期 = '2026-06-17'
union all
select 'CRM' as 系统, 订单号, 客户ID, 订单金额
from CRM_订单表 where 下单日期 = '2026-06-17';
26、查询进阶-开窗函数-sum over
💡 select列名1,列名2,列名3聚合函数**(列名3)over(partition by列名2)as列名4fromA表**
开窗函数是一种强大的查询工具,它允许我们在查询中进行对分组数据进行计算、 同时保留原始行的详细信息 。
开窗函数可以与聚合函数(如SUM、AVG、COUNT等)结合使用,但与普通聚合函数不同,开窗函数不会导致结果集的行数减少。
Excle中的数据透视将明细变成汇总表,行数变少;
SUM OVER在明细表中加上汇总值,行数不变。
ORDRE BY逐行计算;PARTITION BY分组计算。
❓ 假设有一个学生表 student,包含以下字段:id(学号)、name(姓名)、age(年龄)、class_id(班级编号)、score(分数)、exam_num(考试编号)。
请你编写一个 SQL 查询,返回每个学生的详细信息(字段顺序和原始表的字段顺序一致),并计算每个班级的学生平均分(class_avg_score)。
sql
select id,name,age,class_id,score,exam_num,
AVG(score) over(partition by class_id) as class_avg_score from student
❗ 实际工作场景
sql
--物流数据:按港口统计运费排名
select 运单号,港口名称,运费金额,
row_number() over (partition by 港口名称 order by 运费金额 desc) as 本港口运费排名
from 运单主表
--供应商管理:采购订单占比
select 采购订单号,采购品类,采购金额,
sum(采购金额) over (partition by 采购品类) as 本品类采购总额,
round(采购金额 / sum(采购金额) over (partition by 采购品类) * 100, 2) as 本品类采购占比
from 采购订单表;
27、查询进阶-开窗函数-sum over order by
💡 select列名1,列名2,列名3聚合函数**(列名3)over(partition by列名2)as列名4fromA表**
sum over order by,可以实现同组内数据的++累加求和++ 。
❓ 假设有一个学生表 student,包含以下字段:id(学号)、name(姓名)、age(年龄)、score(分数)、class_id(班级编号)。
请你编写一个 SQL 查询,返回每个学生的详细信息(字段顺序和原始表的字段顺序一致),并且按照分数升序 的方式++累加计算每个班级的学生总分++(class_sum_score)。
sql
select id,name,age,score,class_id,
sum(score) over (partition by class_id order by score)
as class_sum_score from student
❗ 实际工作场景
sql
--人资数据:按部门统计员工薪资占部门总薪资比例
select 员工姓名,部门,月薪,
sum(月薪) over (partition by 部门) as 本部门总薪资,
round(月薪 / sum(月薪) over (partition by 部门) * 100, 2) as 占本部门比例
from 员工信息表;
--财务数据:按客户统计应收占款占比
select 客户名称,应收金额,账龄,
sum(应收金额) over (partition by 客户名称) as 本客户总应收,
round(应收金额 / sum(应收金额) over (partition by 客户名称) * 100, 2) as 占本客户比例
from 应收账款表 where 账期 = '2026-06';
28、查询进阶-开窗函数-rank
💡 select列名1,列名2,列名3rank**()over(partition by列名2order by列名3desc)as别名fromA表**
对查询结果集中的行进行 ++排名++ 的开窗函数。它可以根据指定的列或表达式对结果集中的行进行排序,并为每一行分配一个排名。在排名过程中,相同的值将被赋予相同的排名,而不同的值将被赋予不同的排名。
当存在并列(相同排序值)时,Rank 会跳过后续排名,并保留相同的排名。
Rank 开窗函数的常见用法是在查询结果中查找前几名(Top N)或排名最高的行。
其中,PARTITION BY 子句可选,用于指定分组列,将结果集按照指定列进行分组;ORDER BY 子句用于指定排序列及排序方式,决定了计算 Rank 时的排序规则。AS rank_column 用于指定生成的 Rank 排名列的别名。
❓ 假设有一个学生表 student,包含以下字段:id(学号)、name(姓名)、age(年龄)、score(分数)、class_id(班级编号)。
请你编写一个 SQL 查询,返回每个学生的详细信息(字段顺序和原始表的字段顺序一致),并且按照分数降序的方式计算每个班级内的学生的分数排名(ranking)。
sql
select id,name,age,score,class_id,
rank() over(partition by class_id order by score desc)
as ranking from student;
❗ 实际工作场景
sql
--电商数据:各品类商品价格排名
select 商品名称,品类,单价,
rank() over (partition by 品类 order by 单价 desc) as 品类内价格排名
from 商品信息表;
--客诉数据:按投诉类型统计工单处理时长排名
select 工单号,投诉类型,处理时长(分钟),
rank() over (partition by 投诉类型 order by 处理时长 desc) as 该类投诉耗时排名
from 客服工单表
where 处理完成日期 between '2026-06-01' and '2026-06-30';
29、查询进阶-开窗函数-row_number
💡 select列名1,列名2,列名3row_number**()over(partition by列名2order by列名3desc)as别名fromA表**
Row_Number 开窗函数是 SQL 中的一种用于为查询结果集中的每一行 ++分配唯一连续排名++ 的开窗函数。
Row_Number 函数为每一行都分配一个唯一的整数值,不管是否存在并列(相同排序值)的情况。每一行都有一个唯一的行号,从 1 开始连续递增。
❓ 假设有一个学生表 student,包含以下字段:id(学号)、name(姓名)、age(年龄)、score(分数)、class_id(班级编号)。
请你编写一个 SQL 查询,返回每个学生的详细信息(字段顺序和原始表的字段顺序一致),并且按照分数降序的方式给每个班级内的学生分配一个编号(row_number)。
sql
select id,name,age,score,class_id,
row_number() over(partition by class_id order by score desc)
as row_number from student;
❗ 实际工作场景
sql
--成绩数据:每个学生的成绩排名
select 学生姓名,班级,期末总分,
row_number() over (partition by 班级 order by 期末总分 desc) as 班级排名
from 学生成绩表 where 学期编码 = '2026S2';
--销售数据:各区域门店业绩排名
select 门店名称,所属区域,月销售额,
row_number() over (partition by 所属区域 order by 月销售额 desc) as 区域内排名
from 门店月报表 where 月份 = '2026-06';
30、查询进阶-开窗函数-lag/lead
💡 select****列名1,列名2,列名3 lag/lead (列名3,1,null)over(partition by列名1order by列名2**)as别名fromA表**
开窗函数 Lag 和 Lead 的作用是获取在当前行之前或之后的行的值,这两个函数通常在需要++比较相邻行数据++ 或进行++时间序列分析++ 时非常有用。
Lag函数:获取当前行之前的某一列的值,查看上一行的数据。
Lead函数:获取 当前行之后 的某一列的值,查看下一行的数据。
LAG(要获取值的列名, offset向上偏移的行数, 可选参数) OVER (PARTITION BY 列名 ORDER BY 列名)
LEAD(要获取值的列名, offset向下偏移的行数, 可选参数) OVER (PARTITION BY 列名 ORDER BY 列名)
PARTITION BY和ORDER BY子句可选,用于分组和排序数据
❓ 假设有一个学生表 student,包含以下字段:id(学号)、name(姓名)、age(年龄)、score(分数)、class_id(班级编号)。
请你编写一个 SQL 查询,返回每个学生的详细信息(字段顺序和原始表的字段顺序一致),并且按照分数降序的方式获取每个班级内的学生的前一名学生姓名(prev_name)、后一名学生姓名(next_name)。
sql
select id,name,age,score,class_id,
lag(name, 1, null) over (oartition by class_id order by score desc) as prev_name,
lead(name, 1, NULL) over (oartition by class_id order by score desc) as next_name
from student
❗ 实际工作场景
sql
--生产数据:同一产线连续两天的产量对比
select 产线名称,日期,日产量,
lag(日产量, 1, null) over (partition by 产线名称 order by 日期) as 前日产量
from 产线日报表 where 日期 between '2026-06-01' and '2026-06-30';
--物流数据:同一车辆连续运单的费用变化
select 运单号,车辆ID,发货日期,运费金额,
lag(运费金额, 1, null) over (partition by 车辆ID order by 发货日期) as 上一趟运费
from 运单主表 where 发货日期 between '2026-06-01' and '2026-06-30';
------------END