窗口函数是在不聚合行的前提下,基于“窗口”内相关行对每行数据进行计算的强大工具,其核心是OVER()子句定义的窗口范围。与传统聚合函数(如SUM、AVG配合GROUP BY)不同,窗口函数保留原始数据的每一行,同时为每行生成一个基于窗口计算的新值,适用于需保留细节并进行复杂分析的场景。典型结构为:函数(表达式) OVER ([PARTITION BY 列名] [ORDER BY 列名]),其中PARTITION BY将数据分组,ORDER BY确定窗口内行的顺序。常见排名函数包括ROW_NUMBER()(唯一连续编号,无并列)、RANK()(并列后排名跳跃,如1,2,2,4)和DENSE_RANK()(并列后排名连续,如1,2,2,3),选择依据业务对并列的处理需求。窗口函数广泛应用于累计求和、移动平均、前后行比较(LAG/LEAD)、分组极值获取等高级分析场景,显著简化复杂查询,减少子查询与连接操作,提升可读性和执行效率。但其性能受排序开销、内存使用、索引支持和窗口框架影响较大,尤其在大数据集上需合理设计索引、优化分区与排序逻辑,避免不必要的开销。

SQL中的窗口函数,简单来说,就是一种在不聚合行的情况下,对与当前行相关的行集执行计算的强大工具。它允许你在每行数据上,根据一个“窗口”内的其他行来计算一个值,比如排名、累计总和或者移动平均。RANK、ROW_NUMBER等就是这类函数中的佼佼者,它们让复杂的数据分析变得前所未有的简单和高效。
在我看来,理解窗口函数的关键在于那个
OVER()
GROUP BY
一个典型的窗口函数结构是这样的:
窗口函数(表达式) OVER ([PARTITION BY 列名] [ORDER BY 列名 [ASC|DESC]])
这里的
PARTITION BY
ORDER BY
这真的是一个非常核心的问题,也是很多人初学时会感到困惑的地方。说白了,传统聚合函数(比如
SUM()
AVG()
COUNT()
GROUP BY
GROUP BY department_id
SUM(sales)
但窗口函数则完全不同。它们执行的是“行级计算”。它们在计算时确实会考虑一组行(那个“窗口”),但最终结果是为每一行都生成一个值。这意味着你既能看到每笔交易的详细信息,又能在这笔交易旁边看到它在某个特定分组(比如同部门)中的排名,或者它到目前为止的累计销售额。
为什么选择它们?原因很多,但最主要的有以下几点:
RANK()
ROW_NUMBER()
举个例子,假设我们有销售数据: | 订单ID | 部门ID | 销售额 | |---|---|---| | 1 | A | 100 | | 2 | A | 150 | | 3 | B | 200 | | 4 | A | 50 |
如果用传统聚合:
SELECT 部门ID, SUM(销售额) FROM 销售表 GROUP BY 部门ID;
如果用窗口函数计算部门内累计销售额:
SELECT 订单ID, 部门ID, 销售额, SUM(销售额) OVER (PARTITION BY 部门ID ORDER BY 订单ID) AS 部门累计销售额 FROM 销售表;
看,每一行都还在,但又多了一个有用的分析字段。
这三个函数是窗口函数家族中最常用的“排名”函数,但它们处理“并列”情况的方式各不相同,因此适用场景也不同。我常常觉得,理解它们的核心就在于你如何看待并列名次。
这个函数是最直接的。它为分区中的每一行分配一个唯一的、连续的序号,从1开始。 特点: 绝不会出现并列。即使两行在排序条件上完全相同,它们也会得到不同的
ROW_NUMBER
ROW_NUMBER()
rn = 1
ROW_NUMBER()
示例:
SELECT
product_id,
sale_date,
sale_amount,
ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY sale_date DESC) AS rn
FROM
sales_data;这会给每个产品的销售记录按日期倒序编号,最新的销售记录
rn
RANK()
示例:
SELECT
student_id,
score,
RANK() OVER (ORDER BY score DESC) AS student_rank
FROM
exam_results;如果两个学生都考了90分,他们可能都得到
rank = 2
rank
4
DENSE_RANK()
RANK()
RANK()
示例:
SELECT
product_category,
sales_amount,
DENSE_RANK() OVER (PARTITION BY product_category ORDER BY sales_amount DESC) AS category_sales_rank
FROM
product_sales;这会给每个产品类别内的销售额进行排名。如果两个产品销售额并列第一,它们都得到
rank = 1
rank = 2
总的来说,选择哪个函数取决于你对“并列”的业务理解和排名需求的精确定义。
窗口函数远不止排名这么简单,它们在实际业务中有着极其广泛且强大的应用,有时候我甚至觉得它们是SQL分析能力的“核武器”。
计算累计总和 (Running Totals): 比如,计算每天的累计销售额。
SELECT
sale_date,
daily_sales,
SUM(daily_sales) OVER (ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_sales
FROM
daily_sales_report;这里的
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
计算移动平均 (Moving Averages): 比如,计算过去7天的平均销售额,用于趋势分析。
SELECT
sale_date,
daily_sales,
AVG(daily_sales) OVER (ORDER BY sale_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS seven_day_moving_avg
FROM
daily_sales_report;ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
比较当前行与前/后一行 (LAG/LEAD): 比如,计算相邻两次交易之间的时间间隔,或者与前一天的销售额进行比较。
SELECT
order_id,
customer_id,
order_date,
LAG(order_date, 1, NULL) OVER (PARTITION BY customer_id ORDER BY order_date) AS previous_order_date,
DATEDIFF(day, LAG(order_date, 1, NULL) OVER (PARTITION BY customer_id ORDER BY order_date), order_date) AS days_since_last_order
FROM
customer_orders;LAG(order_date, 1, NULL)
order_date
NULL
查找每个分组的最高/最低值 (FIRST_VALUE/LAST_VALUE): 比如,找出每个部门销售额最高的员工姓名。
SELECT
department_id,
employee_name,
sales_amount,
FIRST_VALUE(employee_name) OVER (PARTITION BY department_id ORDER BY sales_amount DESC) AS top_seller_in_dept
FROM
employee_sales;窗口函数虽然强大,但并非没有代价。它们在处理大数据集时,可能会带来显著的性能开销。
OVER()
ORDER BY
PARTITION BY
ORDER BY
PARTITION BY
PARTITION BY
PARTITION BY
ORDER BY
(partition_column, order_column)
ROWS
RANGE
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
UNBOUNDED PRECEDING
UNBOUNDED FOLLOWING
GROUP BY
在实际项目中,我通常会先用窗口函数写出逻辑清晰的查询,然后在测试环境用真实数据量进行性能测试。如果发现性能瓶颈,我会检查索引、调整窗口框架,甚至考虑是否可以通过分阶段处理(比如先聚合部分数据,再应用窗口函数)来优化。性能优化是一个迭代的过程,没有一劳永逸的解决方案。
以上就是SQL中的窗口函数是什么?RANK、ROW_NUMBER等详解的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号