窗口函数:SQL进阶的分水岭,学会它甩开80%的取数员
我是小耶,干运营半路出家的野生DBA——写功课只是为了我踩过的坑,你们别再踩了!
一、没有窗口函数的痛苦回忆
以前想算“每个分类下销售额前3的产品”,没有窗口函数的时候,写法是这样的:
-- 传统写法(不推荐,仅作对比)
SELECT a.product_id, a.category, a.sales, COUNT(*) AS rn
FROM products a
JOIN products b ON a.category = b.category AND a.sales <= b.sales
GROUP BY a.product_id, a.category, a.sales
HAVING COUNT(*) <= 3;
这种写法难以理解、性能差、容易错。窗口函数出现后,一切变得简单。
二、窗口函数一行搞定排名
SELECT product_id, category, sales,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) AS rn
FROM products;
外层加个 WHERE rn <= 3,查询结束。
语法拆解:
ROW_NUMBER():编号函数OVER:定义窗口PARTITION BY category:按分类分组,每组内独立编号ORDER BY sales DESC:组内按销售额降序排列
三、三个排名函数对比
| 函数 | 说明 | 示例结果(销售额100,90,90,80) |
|---|---|---|
ROW_NUMBER() | 唯一编号,不处理并列 | 1,2,3,4 |
RANK() | 并列跳号 | 1,2,2,4 |
DENSE_RANK() | 并列不跳号 | 1,2,2,3 |
实战选择建议:
- 分页取数据(每页10条)→
ROW_NUMBER() - 比赛排名(允许并列但跳过名次)→
RANK() - 工资等级(并列不跳过)→
DENSE_RANK()
四、累计占比(帕累托分析)
SELECT product, sales,
SUM(sales) OVER (ORDER BY sales DESC) / SUM(sales) OVER () AS cum_pct
FROM products;
解释:
SUM(sales) OVER (ORDER BY sales DESC):按销售额降序累计求和SUM(sales) OVER ():全局总和(无PARTITION BY)- 两者相除得到累计占比
典型用法:找到贡献前80%销售额的产品(二八法则)。
五、更多窗口函数实战场景
1. 移动平均(MA3)
SELECT date, sales,
AVG(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS ma3
FROM daily_sales;
2. 同比/环比(LAG / LEAD)
SELECT date, sales,
LAG(sales, 1) OVER (ORDER BY date) AS prev_day_sales,
sales / LAG(sales, 1) OVER (ORDER BY date) - 1 AS growth_rate
FROM daily_sales;
3. 分组内百分比
SELECT category, product, sales,
sales / SUM(sales) OVER (PARTITION BY category) AS pct_in_category
FROM products;
六、性能注意事项
- 窗口函数会生成临时表,如果数据量很大(千万级),注意观察
Created_tmp_disk_tables状态。 - ORDER BY 会排序,如果窗口内数据不需要排序,可以省略
ORDER BY提升性能。 - 部分窗口函数(如
ROW_NUMBER())可以替代 LIMIT分组取TopN,比传统子查询快很多。 - MySQL 8.0+ 才支持窗口函数,低版本需要升级或者用变通写法。
七、快速记忆口诀
分组排名用窗口, PARTITION 分组,ORDER 排序, 三个函数看需求, 累计移动都能算。
八、实战练习建议
找一份订单表,自己尝试:
- 每个用户最近3笔订单
- 每月销售额环比增长率
- 每个商品在所属分类中的销售额百分位
推荐刷题网站:LeetCode 窗口函数专题(难度 中等 ~ 困难)
小耶在手,SQL不愁。
你工作中用到窗口函数最多的场景是什么?评论区分享一下,给新手一些灵感。