Administrator
发布于 2023-07-02 / 13465 阅读
155

MySQL 8.0 窗口函数实战应用

背景:一个写了 60 行的排行榜 SQL

七月做运营后台,产品要「每个城市的销售 Top3」和「月度累计业绩」。第一版我用子查询嵌套,SQL 拉到 60 多行,GROUP BYGROUP BY,执行计划里全是 DEPENDENT SUBQUERY,跑一次 3 秒多。同事提醒我:你们库早升 MySQL 8.0 了,窗口函数不用白不用。

排名场景:ROW_NUMBER vs RANK

「每城市销售 Top3」用 ROW_NUMBER() 开窗,按城市分区、业绩降序:

SELECT city, sales_name, amount, rnk
FROM (
  SELECT city, sales_name, amount,
         ROW_NUMBER() OVER (
           PARTITION BY city
           ORDER BY amount DESC
         ) AS rnk
  FROM sales_daily
  WHERE dt = '2023-07-01'
) t
WHERE rnk <= 3;

注意并列处理:ROW_NUMBER() 不处理并列,同分也排出 1、2、3;要「并列同名次」用 RANK(),要「并列占同号但后续不跳号」用 DENSE_RANK()。之前子查询版本为了处理并列写了额外的计数逻辑,窗口函数一行搞定。

累计场景:SUM OVER 替代自连接

「月度累计业绩」以前要自己和自己 JOIN 出所有更早的日期再求和,复杂度 O(n²)。窗口函数用 ROWS BETWEEN

SELECT sales_name, dt, amount,
       SUM(amount) OVER (
         PARTITION BY sales_name
         ORDER BY dt
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS cum_amount
FROM sales_daily
WHERE dt BETWEEN '2023-07-01' AND '2023-07-31';

执行计划从两次全表扫描 + 排序降到一次顺序扫描,耗时从 2.4 秒掉到 180 毫秒。

移动平均场景:滑动窗口

看趋势常用 7 日移动平均,窗口函数天然支持滑动范围:

SELECT dt, amount,
       AVG(amount) OVER (
         ORDER BY dt
         ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
       ) AS ma7
FROM sales_daily;

替代复杂子查询的收益

写法代码行数查询耗时
子查询嵌套60+3.1s
窗口函数180.2s

逻辑也清晰得多:开窗逻辑在 OVER() 里一目了然,不用脑补几层子查询的关联条件。

小结

MySQL 8.0 的窗口函数是把「难写的分析 SQL」变简单的利器,排名、累计、移动平均三类场景几乎都能一行解决,性能和可读性双提升。但要留意:窗口函数仍要 ORDER BY 列有索引,不然分区内的排序会拖慢。别再手写嵌套子查询了,8.0 早已不是当年的 MySQL。

参考