先说说后端面试里那些绕不开的SQL题。每组取TopN、连续登录、同比环比、累计求和,几乎是必考题。很多同学一看到这类需求,条件反射就是“上窗口函数”。但窗口函数不是用来炫技的,它的核心价值在于表达一种意图:在某个分组和排序范围内进行计算。

会用窗口函数,重点不是死记硬背 row_number() 的语法,而是真正理解 partition(分组)、order(排序) 和 frame(窗口框架) 各自在描述什么。
每组 TopN,窗口函数的经典开场
题目很典型:每个部门取工资最高的 3 个人。
SELECT *FROM ( SELECT employee_id, department_id, salary, ROW_NUMBER() OVER ( PARTITION BY department_id ORDER BY salary DESC ) AS rn FROM employee) tWHERE rn <= 3;
这个 PARTITION BY department_id 就是灵魂——它告诉数据库,每个部门单独开一个排名场。而 ORDER BY salary DESC 决定了排名依据。这比写子查询或者自连接清爽太多了。
窗口函数的执行逻辑,一张图说明白
flowchart TD A[原始数据] --> B[按 partition 分组] B --> C[组内排序] C --> D[计算窗口函数] D --> E[外层过滤]
窗口函数跟 group by 最大的区别是什么?前者是在结果行上“附加”一个计算值,不会把多行数据压成一行。这是很多新手容易混淆的地方——以为窗口函数会改变行数,其实它不会。
row_number、rank、dense_rank:并列怎么处理?
假设工资是 [100, 100, 90]:
row_number: 1, 2, 3rank: 1, 1, 3dense_rank: 1, 1, 2
面试时别想当然地直接用 row_number。题目说“取前三名”,那并列的两个人算不算都要保留?一定要先问清楚业务意图。这个细节比你会写哪种函数重要得多。
性能这块,大多数人容易忽略
窗口函数本质上需要排序。大表上跑窗口函数,笔者的建议是:先考虑分区字段、排序字段和过滤条件。能先缩小数据范围,就别全表开窗。
WHERE dt = '2026-07-03'
就是这么一句简单的分区过滤,往往比你花里胡哨地换十种窗口写法都管用。
连续登录也是高频题。思路核心是:用日期减去排名,构造出一个分组键。同一段连续日期内,这个差值是一样的。
SELECT user_id, MIN(login_date), MAX(login_date), COUNT(*)FROM ( SELECT user_id, login_date, DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY login_date ) DAY) AS grp FROM user_login) tGROUP BY user_id, grp;
这道题的关键是:把“连续”这个抽象概念,转成具体的“同组”标识。窗口函数只是帮你实现这个转化的工具,核心洞察才是真正的答案。
还有一个经常被忽略的细节:同一天登录多次,算一次还是多次?题目没说,得先弄清楚去重规则。
同比环比场景下,LAG 函数很常用。它能拿到上一行的数据,但上一行是不是昨天、上周或者上月,取决于排序和数据是否完整。如果日期本身不连续,直接用 LAG 可能拿到一个完全错误的基准。
SELECT dt, gmv, LAG(gmv, 1) OVER (ORDER BY dt) AS prev_gmvFROM ads_daily_gmv;
这段代码只适合日期连续的场景。如果日期有空缺,需要先补一张日历表。面试时能指出这个边界条件,会让面试官觉得你考虑得很周全。
总结
SQL 窗口函数的真正价值,在于能够清晰地表达“在分组和排序范围内进行计算”的意图,无论是排名、累计、还是偏移量。重点是把 partition、order、frame 这三个概念理解透,而不是机械地背几个函数名。
窗口函数不是为了写得高级,而是为了把“组内计算”这件事说清楚、写简洁。
