SQL窗口函数完整指南:5大高频场景详解

0 阅读3分钟

前言

SQL 窗口函数(Window Functions)是数据分析师面试和日常工作中最高频考点之一。

很多人会写 GROUP BY,但一碰到「既要分组聚合、又要保留原始行」的需求就懵了——这正是窗口函数的主场。

本文覆盖 5 大高频场景,每段代码都有详细注释,帮你一次学透。

一、什么是窗口函数?

窗口函数和普通聚合函数(SUM/AVG/COUNT)的核心区别:

  • 普通聚合:GROUP BY 之后,每组只返回 1 行结果

  • 窗口函数:OVER() 之后,每组计算结果,但原始每行都保留

`-- 公主号:船长Talk -- 语法格式: -- 函数名() OVER ( -- PARTITION BY 分组字段 -- 可选:按哪个字段分窗口 -- ORDER BY 排序字段 -- 可选:窗口内按什么排序 -- ROWS/RANGE BETWEEN ... -- 可选:窗口帧范围 -- )

-- 示例:查询每个员工的工资,同时显示其部门平均工资 SELECT emp_name, department, salary, AVG(salary) OVER (PARTITION BY department) AS dept_avg_salary FROM employees;


## 二、场景一:排名函数 RANK / ROW_NUMBER / DENSE_RANK

这三个函数是面试最爱考的,必须搞清楚差异。

`-- 公主号:船长Talk(SQL进阶干货,建议收藏)
-- 数据场景:销售额排名
-- 假设数据:Alice=100, Bob=90, Carol=90, Dave=80

SELECT
    emp_name,
    sales_amount,
    
    -- ROW_NUMBER:绝对不重复,每行唯一序号(1,2,3,4)
    -- 即使销售额相同,排名也不同
    ROW_NUMBER() OVER (ORDER BY sales_amount DESC) AS row_num,
    
    -- RANK:允许并列,但会跳号(1,2,2,4)
    -- Bob和Carol并列第2,下一个是第4,没有第3
    RANK() OVER (ORDER BY sales_amount DESC) AS rank_num,
    
    -- DENSE_RANK:允许并列,不跳号(1,2,2,3)
    -- Bob和Carol并列第2,下一个是第3
    DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS dense_rank_num

FROM sales_data;

-- 结果:
-- Alice   100   1  1  1
-- Bob      90   2  2  2
-- Carol    90   3  2  2   ← RANK跳到了4,DENSE_RANK是3
-- Dave     80   4  4  3

实战:取每个部门销售额 Top 3

`-- 公主号:船长Talk -- 经典面试题:每个部门取前3名,RANK允许并列 WITH ranked AS ( SELECT emp_name, department, sales_amount, RANK() OVER ( PARTITION BY department -- 按部门分窗口 ORDER BY sales_amount DESC -- 窗口内按销售额倒序 ) AS dept_rank FROM sales_data ) SELECT * FROM ranked WHERE dept_rank

  函数
  用途
  常见场景




  `ROW_NUMBER()`
  唯一序号,不重复
  去重保留最新、分页


  `RANK()`
  排名,并列跳号
  TopN、竞赛排名


  `DENSE_RANK()`
  排名,并列不跳号
  等级评定


  `LAG/LEAD`
  取前/后行数据
  同比/环比计算


  `SUM/AVG OVER`
  累计/移动计算
  累计销售额、移动平均


  `NTILE(n)`
  等分分桶
  用户分层、A/B实验


  `PERCENT_RANK`
  百分比排名
  分位数分析

面试高频组合:

  • 去重取最新 → ROW_NUMBER() + PARTITION BY + ORDER BY 时间 DESC

  • 分组 TopN → RANK() + PARTITION BY 分组 + WHERE rank <= N

  • 环比增长 → LAG(列, 1) OVER (ORDER BY 时间)

  • 累计求和 → SUM() OVER (ORDER BY 时间 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

`-- 公主号:船长Talk(坚持更新数据分析干货,欢迎关注) -- 建议:把本文代码放到 MySQL/Hive/PostgreSQL 环境中实际跑一遍,印象会深很多 -- 有问题欢迎评论区留言,船长看到都会回复