应分别计算两期rank()再join对比:先用rank() over(order by amount desc)为各省销售额独立排名,再按省份full outer join对齐,用coalesce处理null,确保并列占位与空缺省份逻辑正确。

用RANK()两次计算再JOIN对比最直接
想看各省份销售额排名的变动,核心不是“一次算出变化”,而是分别算出两期的RANK(),再按省份对齐比较。硬要在单个窗口函数里“动态比上期”会绕进逻辑陷阱——窗口函数不跨行访问历史结果,RANK()本身也不保存上下文。
典型场景:你有sales_2023和sales_2024两张表(或同一张表带year字段),每行含province和amount。要得出“2024年比2023年升/降几名”。
- 必须先各自按销售额降序排,用
RANK() OVER (ORDER BY amount DESC),别用ROW_NUMBER()——并列时排名需一致 - 注意NULL值:若某省2024年没数据,
LEFT JOIN后其2024排名为NULL,差值就变成NULL,需用COALESCE(rnk_2024, 0)等明确补位逻辑 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle都支持;SQLite需3.25+且开启窗口函数
处理并列排名与空缺省份的关键细节
真实销售数据常有并列(如江苏和浙江都是1200万),RANK()会给出相同名次(比如都是第3),下一名跳到第5——这正是业务需要的“并列占位”逻辑。但如果你用DENSE_RANK(),并列后下一名不跳号(3,3,4),会导致变化值失真(例如从第3→第4只降1名,实际可能被3个省反超)。
空缺省份更易踩坑:2023年有西藏(第31名),2024年没数据 → JOIN后rnk_2024为NULL,rnk_2023 - rnk_2024直接是NULL。必须显式处理:
SELECT COALESCE(t1.province, t2.province) AS province, t1.rnk_2023, t2.rnk_2024, COALESCE(t1.rnk_2023, 999) - COALESCE(t2.rnk_2024, 999) AS rank_change FROM ( SELECT province, RANK() OVER (ORDER BY amount DESC) AS rnk_2023 FROM sales_2023 ) t1 FULL OUTER JOIN ( SELECT province, RANK() OVER (ORDER BY amount DESC) AS rnk_2024 FROM sales_2024 ) t2 ON t1.province = t2.province;
注意:FULL OUTER JOIN才能捕获单边缺失(如新增省份或退出省份);若数据库不支持(如MySQL),改用UNION ALL模拟,但逻辑更冗长。
用CTE避免重复写RANK(),提升可读性
如果还要加筛选(比如只看销售额>500万的省份)或后续聚合(如统计“上升省份数”),把两期RANK()封装进CTE比嵌套子查询更清晰,也方便复用:
WITH rnk_2023 AS ( SELECT province, amount, RANK() OVER (ORDER BY amount DESC) AS rnk FROM sales_2023 WHERE amount > 500 ), rnk_2024 AS ( SELECT province, amount, RANK() OVER (ORDER BY amount DESC) AS rnk FROM sales_2024 WHERE amount > 500 ) SELECT COALESCE(a.province, b.province) AS province, a.rnk AS rnk_2023, b.rnk AS rnk_2024, (COALESCE(a.rnk, 999) - COALESCE(b.rnk, 999)) AS delta FROM rnk_2023 a FULL OUTER JOIN rnk_2024 b ON a.province = b.province ORDER BY delta;
这里delta为正表示下降(2023名次更靠前),负表示上升。业务方常混淆方向,建议在字段别名里直接写清rank_change_2024_vs_2023。
性能和兼容性:小数据放心用,大数据注意索引
RANK()本身是窗口函数,执行时需全量排序,若单表超千万行,两次排序+JOIN可能慢。优化点很实际:
- 确保
province和amount上有联合索引:CREATE INDEX idx_p_a ON sales_2024 (amount DESC, province);—— 虽不能跳过排序,但加速扫描 - PostgreSQL中可考虑物化CTE(
MATERIALIZED)避免重复计算,但MySQL不支持 - 如果只要Top 10变动,可在每个CTE里加
LIMIT 10,但注意:RANK()必须在LIMIT前完成,否则排名失效
真正容易被忽略的是“并列处理一致性”:两期必须用完全相同的ORDER BY表达式(包括NULLS FIRST/LAST),否则同分省份在两年间排名可能因NULL顺序不同而错位。哪怕数据里没NULL,也显式写上ORDER BY amount DESC NULLS LAST更稳妥。










