「sql之窗口函数」

窗口函数

最近学习sql的时候,发现了一个比较难理解的语法---窗口函数

先说定义:「看完定义你可能觉得一头雾水,哈哈别急,等我后续一步步拆解」窗口函数是 SQL 中一种用于在查询结果集的特定 "窗口"(子集)上进行计算的函数。它能够在不改变原始数据行的基础上,为每行数据计算出一个聚合值或排名值等相关结果。与普通聚合函数(如 SUM、AVG 等)不同,聚合函数会将多行数据聚合成一行输出,而窗口函数可以在每一行数据上都返回一个计算结果。

表达式

sql 复制代码
function(args) OVER([PARTITION BY expression] [ORDER BY expression[ASC|DESC]][frame])

表达式拆解:

首先,OVER,PARTITION BY,ORDER BY 都是关键字,记住就好啦,之后越用越习惯,我们主要来拆解一下他们的作用

1)function(args)

这个函数的作用就是在指定窗口中对数据进行计算或处理,以得出所需的结果。「至于这里说的窗口是什么,请往下看」

function(args)可以是聚合函数,也可以是排名函数,具体使用什么函数就和不同的业务相关了,我在这里列出几个常用的函数:

聚合函数

  • SUM(args):计算分区内指定列的总和。「这里的分区和窗口是一个概念,以下同理」
  • AVG(args):计算分区内指定列的平均值。
  • MIN(args):找出分区内指定列的最小值。
  • MAX(args):找出分区内指定列的最大值。

排名函数

  • RANK():为分区内的行分配排名,相同的值会获得相同的排名,并且会跳过相同排名的数量。
  • DENSE_RANK():为分区内的行分配排名,相同的值会获得相同的排名,但不会跳过相同排名的数量。
  • ROW_NUMBER():为分区内的行分配一个唯一的连续整数,从 1 开始,无论行内的值是否相同。(关于这三个排名函数的区别,下面会详细介绍➕做题练习)
2)[PARTITION BY expression]

[PARTITION BY expression]其中,PARTITION BY 是关键字,记住即可,而后面的 expression,就是我们要指定一个分组,这个字段通俗理解就是,我们要按照什么进行分组,当然他是可选的

3)[ORDER BY expression[ASC|DESC]]

ORDER BY 就好理解了,就是要指定我们要按照什么进行排序,默认是升序,如果要降序排序,需要我们进行显示的指定

4)[frame]

最后一部分就是这个[frame]了,他其实是指定一个窗口大小,也是可选的,其中他又分为两种模式,分别是ROWS BETWEENRANGE BETWEEN,下面我们来简单说一下这两种模式的区别:

ROWS BETWEEN:按物理行来进行划分窗口,明确指定了窗口包含的行数,从当前行开始,向前或向后延伸一定的行数。

  • ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:窗口从分区的第一行开始,到当前行结束。
  • ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:窗口包含当前行的前一行、当前行和后一行,总共三行。

RANGE BETWEEN:按一定的数值逻辑来划分窗口范围,该范围是根据 ORDER BY 子句中列的值的范围来确定的,而不是物理行。通常与 ORDER BY 子句中的排序字段的逻辑值范围相关。

  • RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:窗口包含从分区的第一行开始,到当前行**(基于** ORDER BY **列的值)**的所有行。
  • RANGE BETWEEN 5 PRECEDING AND 5 FOLLOWING:窗口包含 ORDER BY 列的值在当前行的值的前 5 个值到后 5 个值之间的所有行,而不是具体的物理行。

关键区别总结

  • ROWS BETWEEN 基于物理行,根据实际的行位置来确定窗口范围,对于需要精确控制前后行数量的计算(如移动平均、相邻行对比等)很有用。
  • RANGE BETWEEN 基于逻辑范围 ,根据 ORDER BY 列的的范围来确定窗口范围,更适合处理需要根据排序字段的值的范围进行计算的情况,例如基于数据值范围的聚合计算,对数据的逻辑分组和分析更有帮助。

大家注意体会上述的物理行逻辑值的关系~~~

针对[frame],我再列出一些他的常用表达式和对应的含义,大家可以对照着看看,一定可以理解~

ROWS BETWEEN 常用表达式

  • ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:分区范围为从起始行到当前行
  • ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING:分区范围为从当前行到最后一行
  • ROWS BETWEEN n PRECEDING AND m FOLLOWING:从当前行的前 n 行开始,到当前行的后 m 行结束

RANGE BETWEEN 常用表达式

  • RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:此窗口范围从该分区中排序字段最小的行开始,到当前行结束。

RANGE BETWEEN 的表达式不好理解,我们举一个具体的例子来看:

sql 复制代码
SELECT 
    product_id, 
    sales_date, 
    sales_amount, 
    SUM(sales_amount) OVER (
        PARTITION BY product_id 
        ORDER BY sales_date 
        RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS cumulative_sales_amount
FROM 
    sales;

解释 :对于每个 product_id 分区,按 sales_date 排序。此窗口范围从该分区中 sales_date 最小的行开始,到当前行(根据 sales_date 的值)结束。cumulative_sales_amount 计算的是从该分区中 sales_date 最小的行到当前行的 sales_amount 的累计和。它会考虑 sales_date 值小于或等于当前行 sales_date 的所有行,而不是具体的行数。

  • RANGE BETWEEN n PRECEDING AND m FOLLOWING:此窗口范围包括 sales_date 值在当前行 sales_date 减去 n(这里 n = 10)到当前行 sales_date 加上 m(这里 m = 10)范围内的所有行。

举例:

sql 复制代码
SELECT 
    product_id, 
    sales_date, 
    sales_amount, 
    AVG(sales_amount) OVER (
        PARTITION BY product_id 
        ORDER BY sales_date 
        RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING
    ) AS range_average

解释 :对于每个 product_id 分区,按 sales_date 排序。此窗口范围包括 sales_date 值在当前行 sales_date 减去 n(这里 n = 10)到当前行 sales_date 加上 m(这里 m = 10)范围内的所有行。range_average 计算的是这些行的 sales_amount 的平均值。请注意,这里的范围是基于 sales_date 的值,而不是物理行,因此如果有多个行的 sales_date 在这个范围内,它们都会被考虑进去。

排序函数的区别

函数记忆关键点:

  • row_number():序号不重复且连续
  • rank():序号可以重复,但序号不连续(比如:两个同学成绩都是 100 并列第一,第三个同学排名就是第三)
  • dense_rank():序号可以重复,序号连续

练习

LeetCode 178. 分数排名

sql 复制代码
SELECT
  Scores.score,
  -- 使用 DENSE_RANK() 窗口函数对成绩进行排名
  -- 该函数会为结果集中的每一行分配一个排名,根据分数降序排列
  -- 相同分数的行将获得相同的排名,并且排名不会跳过,即下一个不同分数的排名会紧挨着上一个不同分数的排名
  dense_rank() over (
    -- 按照分数降序排列
    ORDER BY
      score DESC
  ) AS `rank`
FROM
  Scores;

LeetCode 184. 部门工资最高的员工

sql 复制代码
select
  Department,
  Employee,
  Salary
from
  (
    select
      d.name as Department,
      e.name as Employee,
      e.salary as Salary,
      -- 窗口函数,根据部门ID分组后再根据员工薪水降序排序的顺序进行排名
      rank() over (
        partition by
          e.departmentId
        order by
          e.salary desc
      ) as salary_rank
    from
      Employee as e
      inner join Department as d on e.departmentId = d.id
  ) as t
where
  -- 取排名为1的员工就是部门最高薪水的员工
  salary_rank = 1;

总结

看到这里,再来回看一下窗口函数的定义:

窗口函数是 SQL 中一种用于在查询结果集的特定 "窗口"(子集)上进行计算的函数。它能够在不改变原始数据行的基础上,为每行数据计算出一个聚合值或排名值等相关结果。与普通聚合函数(如 SUM、AVG 等)不同,聚合函数会将多行数据聚合成一行输出,而窗口函数可以在每一行数据上都返回一个计算结果。

已经很清晰啦~~

相关推荐
上海拔俗网络28 分钟前
“AI智能服务平台系统,让生活更便捷、更智能
java·团队开发
计算机徐师兄31 分钟前
Java基于SSM框架的在线视频教育系统小程序【附源码、文档】
java·小程序·在线视频教育系统小程序·在线视频教育·在线视频教育微信小程序·视频教育
爱上语文2 小时前
Http请求响应——响应
java·开发语言·网络协议·http
泥水沟的胖头鱼3 小时前
dbeaver创建create临时表之后查询不到问题排查
数据库·sql
PhoenixFlyAway4 小时前
datahub汉化之路
数据库
Jack_hrx4 小时前
全面教程:Nacos 2.3.2 启用鉴权与 MySQL 数据存储配置
java·nacos·nacos鉴权·nacos配置mysql
一决威严-雪雪5 小时前
springboot整合gateway
java·开发语言
我的运维人生6 小时前
MySQL深度解析:高性能优化与实战案例
数据库·mysql·性能优化·运维开发·技术共享
凉秋girl6 小时前
Java基础概念
java·开发语言
ChoSeitaku6 小时前
No.1|Godot|俄罗斯方块复刻|棋盘和初始方块的设置
java·前端·godot