SQL如何用MAX OVER函数统计每个商品的历史最高价格

来源:APP编程网作者:比特币程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《SQL如何用MAX OVER函数统计每个商品的历史最高价格》,敬请观看详情。在分析销售数据时,常常需要保留每一笔交易记录,同时知道该商品截止当前或全周期的历史最高价。传统写法要么用关联子查询导致性能急剧下降,要么用GROUP BY丢失明细行。窗口函数MAX OVER恰好能在一行结果中同时输出原始价格与分区内最大值。它按商品编号划分窗口,在不开聚合的情况下计算组内最大价格,既保留订单明细又避免自连接。理解PARTITION BY与ORDER BY在MAX OVER中的差异,能帮助我们在亿级流水表里高效产出报表,而不必改写整体查询结构。

在电商或库存系统的数据表中,我们往往既想看到每一笔成交的价格,又希望顺带知道这个商品到目前为止出现过的最贵价格。使用普通的GROUP BY会把明细行合并掉,而关联子查询在大数据量下又慢得难以接受。窗口函数里的MAX OVER可以原地算出每个商品的历史最高价,同时不破坏原有记录行。

SQL如何用MAX OVER函数统计每个商品的历史最高价格

一、什么是MAX OVER窗口函数

MAX OVER是SQL标准中的窗口聚合函数,它在指定分区(PARTITION BY)内计算最大值,但不会像普通聚合那样把多行压缩成一行。每一行都会附加一个所在窗口的MAX结果,因此原始明细被完整保留。这种特性让它非常适合做“带维度的极值标注”。

基本语法为MAX(列名) OVER (PARTITION BY 分区列 [ORDER BY 排序列])。当省略ORDER BY时,MAX取整个分区的最大值;加上ORDER BY后,默认取从窗口第一行到当前行的累积最大值,这在统计“截至当日历史最高”时非常有用。

二、建表与示例数据

我们先构造一张简单的商品价格流水表,包含商品编号、成交日期与成交价格,便于后续演示MAX OVER的不同写法。

CREATE TABLE product_price (
    product_id INT,
    sale_date DATE,
    price DECIMAL(10,2)
);

INSERT INTO product_price VALUES
(1, '2023-01-01', 10.00),
(1, '2023-01-05', 15.00),
(1, '2023-01-10', 12.00),
(2, '2023-01-02', 20.00),
(2, '2023-01-08', 18.00);

上面的数据里,商品1的最高价是15,商品2的最高价是20。如果我们直接GROUP BY product_id,就只能拿到这两个数字,看不到每天的价格明细。接下来用窗口函数解决这个矛盾。

三、统计每个商品的全周期历史最高价

最简单的形式是不加ORDER BY,仅按商品分区,此时MAX OVER会返回该商品在所有已知记录里的最高价格。

SELECT
    product_id,
    sale_date,
    price,
    MAX(price) OVER (PARTITION BY product_id) AS max_price_all
FROM product_price
ORDER BY product_id, sale_date;

执行后,商品1的三行都会带上15.00,商品2的两行都会带上20.00。这种写法常用来给明细行打上“全局最高价”标签,比如做价格异常预警时,可以一眼看出哪天卖得比历史峰值低很多。

从执行计划角度看,数据库只需对分区做一次排序或哈希分组,再广播最大值给分区内各行,成本远低于关联子查询。在千万级数据上,通常能从几十秒降到毫秒级响应。

四、统计截至当日的累积历史最高价

如果业务关心的是“到这一天为止,历史上最高卖过多少”,就需要在OVER子句里加ORDER BY。此时窗口默认从分区首行滑动到当前行。

SELECT
    product_id,
    sale_date,
    price,
    MAX(price) OVER (
        PARTITION BY product_id
        ORDER BY sale_date
    ) AS max_price_so_far
FROM product_price
ORDER BY product_id, sale_date;

以商品1为例:1月1日最高就是10;1月5日变成15;1月10日虽然当天是12,但累积最高仍是15。这比全周期最高多了时间维度上的渐进信息,适合绘制价格爬坡曲线。

需要注意的是,ORDER BY产生的默认窗口是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。如果同一天有多条相同商品记录,它们会被视作同一排序值并共享累积最大值,不会出现前后行不一致的问题。

五、与传统写法的对比

在不支持窗口函数的旧版本数据库里,开发者常用关联子查询实现类似需求,例如下面这段:

SELECT
    a.product_id,
    a.sale_date,
    a.price,
    (SELECT MAX(b.price)
     FROM product_price b
     WHERE b.product_id = a.product_id) AS max_price_all
FROM product_price a;

这段代码逻辑正确,但每一行都要触发一次子查询扫描,复杂度是O(N^2)。当表扩大到百万行,数据库很容易超时。而MAX OVER只需一次分区扫描,代码也更短,可读性明显更好。

另外,有些人尝试用自连接再GROUP BY,同样会临时膨胀数据量。窗口函数由于不改变行数,在IO和内存上都有优势,是现代化SQL报表的首选方案。

六、常见误区与注意事项

一个容易踩的坑是:以为PARTITION BY和GROUP BY效果一样。实际上GROUP BY会折叠行,MAX OVER不会。若误把聚合结果与窗口结果混用,可能导致报表行数不对。

另一个误区是在ORDER BY后误以为MAX只取“当前行的值”。如前所述,默认框架是到当前行之前的所有行,所以它能反映历史峰值。若只想看当前行自身,应使用MAX(price) OVER (PARTITION BY product_id ORDER BY sale_date ROWS BETWEEN CURRENT ROW AND CURRENT ROW),但那种写法失去了历史含义,一般很少用。

写法返回行数时间含义性能
GROUP BY每商品一行全周期中等
关联子查询原样明细全周期较差
MAX OVER无ORDER BY原样明细全周期
MAX OVER有ORDER BY原样明细累积至当日

七、实战组合技巧

我们可以把历史最高价和当前价做差,快速找出折扣力度。例如下面语句直接算出商品低于峰值多少:

SELECT
    product_id,
    sale_date,
    price,
    MAX(price) OVER (PARTITION BY product_id) AS peak,
    MAX(price) OVER (PARTITION BY product_id) - price AS diff
FROM product_price;

这种衍生列在运营分析中极其实用,既不用临时表,也不用多层嵌套。配合视图封装,前端直接拖拽字段就能出图。

总体而言,MAX OVER以极低的认知成本和执行开销,解决了明细与聚合共存的经典难题。只要分清带不带ORDER BY的语义差别,就能灵活应对各种历史极值统计场景。

SQLMAX_OVER窗口函数修改时间:2026-07-31 17:06:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。