SQL窗口函数是数据库查询中用于处理分组内数据的高阶功能,它能在不改变原表行数的前提下,对每组数据进行计算并返回结果,在数据分析、报表统计等场景中应用十分广泛,也是技术面试中数据库相关岗位的高频考察点。

什么是SQL窗口函数
窗口函数也叫分析函数,它和聚合函数的区别在于,聚合函数会将多行数据聚合成一行,而窗口函数会为每一行数据返回一个计算结果,同时保留原表的所有行。窗口函数的基本语法结构如下:
-- 窗口函数基本语法
函数名(参数) OVER (
[PARTITION BY 分组列]
[ORDER BY 排序列 [ASC|DESC]]
[窗口范围]
)
其中OVER子句是窗口函数的核心,用来定义数据的窗口范围,PARTITION BY用来指定分组的列,ORDER BY用来指定组内排序的规则,窗口范围可以指定计算的数据范围,比如从当前行往前数几行等。
常见窗口函数分类
面试中常考的窗口函数主要分为以下几类:
- 排名类窗口函数:包括
ROW_NUMBER()、RANK()、DENSE_RANK(),用来对数据进行排名 - 聚合类窗口函数:包括
SUM()、AVG()、COUNT()等聚合函数结合OVER子句使用,用来计算累计、移动聚合值 - 取值类窗口函数:包括
LAG()、LEAD()、FIRST_VALUE()、LAST_VALUE(),用来获取相邻行或者组内首尾行的数据
高频面试题与解题思路
面试题1:查询每个部门薪资最高的前两名员工
这类排名问题是窗口函数最典型的考察场景,我们可以根据需求选择对应的排名函数:
-- 创建测试员工表
CREATE TABLE employee (
id INT,
name VARCHAR(50),
dept VARCHAR(50),
salary INT
);
-- 插入测试数据
INSERT INTO employee VALUES
(1, '张三', '技术部', 12000),
(2, '李四', '技术部', 15000),
(3, '王五', '技术部', 15000),
(4, '赵六', '产品部', 10000),
(5, '钱七', '产品部', 13000),
(6, '孙八', '产品部', 11000);
-- 查询每个部门薪资最高的前两名员工,使用DENSE_RANK避免并列薪资跳过排名
SELECT
id,
name,
dept,
salary,
rk
FROM (
SELECT
id,
name,
dept,
salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
FROM employee
) t
WHERE rk <= 2;
这里需要注意三个排名函数的区别:ROW_NUMBER()会生成连续不重复的排名,即使薪资相同也会排不同名次;RANK()遇到相同值会并列排名,下一个排名会跳过占用的名次;DENSE_RANK()并列排名后下一个排名不会跳过,更符合取前N名不限制人数的需求。
面试题2:计算每个月的累计销售额
这类累计计算问题适合使用聚合类窗口函数,通过指定窗口范围实现累计统计:
-- 创建销售表
CREATE TABLE sales (
month VARCHAR(10),
amount INT
);
-- 插入测试数据
INSERT INTO sales VALUES
('2024-01', 1000),
('2024-02', 1500),
('2024-03', 1200),
('2024-04', 1800);
-- 计算每月累计销售额
SELECT
month,
amount,
SUM(amount) OVER (ORDER BY month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total_amount
FROM sales;
这里的窗口范围ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW表示从第一行到当前行的所有数据,也就是累计计算的逻辑。如果不指定窗口范围,默认就是整个分组内的所有行,同样可以实现累计效果。
面试题3:查询每个用户相邻两次登录的时间间隔
这类需要获取相邻行数据的场景,适合使用LAG()或者LEAD()函数:
-- 创建用户登录表
CREATE TABLE user_login (
user_id INT,
login_time DATETIME
);
-- 插入测试数据
INSERT INTO user_login VALUES
(1, '2024-03-01 10:00:00'),
(1, '2024-03-03 14:00:00'),
(1, '2024-03-05 09:00:00'),
(2, '2024-03-02 11:00:00'),
(2, '2024-03-04 16:00:00');
-- 查询每个用户相邻两次登录的时间间隔
SELECT
user_id,
login_time,
LAG(login_time) OVER (PARTITION BY user_id ORDER BY login_time) AS last_login_time,
TIMESTAMPDIFF(DAY, LAG(login_time) OVER (PARTITION BY user_id ORDER BY login_time), login_time) AS login_interval
FROM user_login;
LAG(列名, n)用来获取当前行往前第n行的数据,默认n为1,LEAD()则是获取往后第n行的数据,这两个函数在处理时间序列相关的相邻数据问题时非常实用。
窗口函数使用注意事项
- 窗口函数只能出现在
SELECT子句和ORDER BY子句中,不能出现在WHERE、GROUP BY等子句中 - 如果
OVER子句中没有PARTITION BY,则默认将整张表作为一个分组 - 不同数据库的窗口函数支持程度略有差异,比如MySQL从8.0版本才开始支持窗口函数,使用时需要确认数据库版本
- 在使用排名函数时,一定要明确业务需求,选择合适的排名函数,避免结果不符合预期
窗口函数的核心逻辑是定义好数据的分组和排序规则,再根据需求选择对应的函数,多结合业务场景练习就能快速掌握其使用技巧,应对面试中的相关问题也会更加轻松。