MySQL实现产品多日期销量转置(Pivot)查询及性能优化方案
解决产品销量行转列(Pivot)及性能优化问题
很高兴看到你最后用SUM函数解决了问题,运行时间从8分钟降到5秒,这提升真的太给力了!我来帮你梳理下问题的根源和正确的实现思路,也给其他遇到类似需求的朋友做个参考~
问题根源分析
你最初用COUNT函数的问题在于:COUNT会统计所有非NULL值的行数,而你的表达式COUNT(IF(DATE(day) = ..., quantity, 0))里,不管条件是否匹配,返回的都是非NULL值(要么是quantity,要么是0),所以最终每个c0/c1/c2得到的都是该产品在三天内的总记录数,而不是销量总和——这就是为什么所有日期的统计结果都相同。
换成SUM就完全正确了:SUM会把符合条件的quantity累加,不符合的加0,正好得到对应日期的销量,无销量时自然显示0。
正确的实现SQL
基础版本(包含三天内有销量的产品)
这个查询会返回所有在最近三天有销量的产品,按总销量降序排序:
SELECT product, SUM(IF(DATE(day) = UTC_DATE(), quantity, 0)) AS c0, -- 当日销量 SUM(IF(DATE(day) = UTC_DATE() - INTERVAL 1 DAY, quantity, 0)) AS c1, -- 昨日销量 SUM(IF(DATE(day) = UTC_DATE() - INTERVAL 2 DAY, quantity, 0)) AS c2, -- 前日销量 SUM(quantity) AS total_sales -- 用于排序的总销量 FROM sales WHERE DATE(day) BETWEEN UTC_DATE() - INTERVAL 2 DAY AND UTC_DATE() -- 过滤仅三天的数据,提升性能 GROUP BY product ORDER BY total_sales DESC;
进阶版本(包含所有产品,即使三天无销量)
如果需要展示所有产品(哪怕最近三天完全没销量),可以先获取所有产品的列表,再通过LEFT JOIN关联销量数据:
WITH all_products AS ( SELECT DISTINCT product FROM sales ) SELECT ap.product, COALESCE(SUM(IF(DATE(s.day) = UTC_DATE(), s.quantity, 0)), 0) AS c0, COALESCE(SUM(IF(DATE(s.day) = UTC_DATE() - INTERVAL 1 DAY, s.quantity, 0)), 0) AS c1, COALESCE(SUM(IF(DATE(s.day) = UTC_DATE() - INTERVAL 2 DAY, s.quantity, 0)), 0) AS c2, COALESCE(SUM(s.quantity), 0) AS total_sales FROM all_products ap LEFT JOIN sales s ON ap.product = s.product AND DATE(s.day) BETWEEN UTC_DATE() - INTERVAL 2 DAY AND UTC_DATE() GROUP BY ap.product ORDER BY total_sales DESC;
额外性能优化提示
你已经通过SUM替代COUNT大幅提升了速度,再加上WHERE条件过滤掉非目标日期的数据,能进一步减少数据库需要处理的行数,让查询更快——这也是你现在查询能秒出结果的原因之一。
内容的提问来源于stack exchange,提问作者sqlnewbie




