每个类别销量Top3产品查询的SQL错误分析及优化方案咨询
问题分析与优化方案
咱们先聊聊你的SQL语句为啥没得到预期结果,再给你推荐个更高效的实现方式~
你的SQL存在的问题
NULL值导致的过滤失效
你的cat2类别里,prod8和prod10没有对应的订单记录,所以它们的sales计算结果是NULL。当你在子查询里取cat2的销量排序后,顺序是10, 1, NULL,offset 2 rows取到的是NULL。而SQL里任何值和NULL比较的结果都是未知(UNKNOWN),会被当作false处理,所以cat2里所有产品都不满足p2.sales >= NULL的条件,自然没出现在结果里。性能低下的嵌套子查询
你嵌套了多层子查询,而且每个产品都单独执行一次sum(quantity)的子查询,相当于对OrderItems表做了N次全表扫描(N是产品总数),数据量越大,性能越差。并列排名的处理缺陷
如果有多个产品销量并列第三,你的写法会漏掉这些并列的产品,因为用了distinct和offset取单一值,没有考虑排名的并列情况。
更优的实现方式:使用窗口函数
在SQL Server(2008及以上版本)中,用窗口函数ROW_NUMBER()或DENSE_RANK()是处理分组排名最简洁高效的方案,逻辑清晰且性能更好。
方案1:获取每个类别销量前三(不保留并列排名)
如果只需要每个类别严格的前三(即使有并列,只取其中一个),用ROW_NUMBER():
WITH ProductSales AS ( -- 先一次性计算所有产品的总销量,LEFT JOIN保证无订单的产品也被统计 SELECT p.id, p.category, ISNULL(SUM(oi.quantity), 0) AS sales -- 把NULL销量转为0,避免排名异常 FROM Products p LEFT JOIN OrderItems oi ON p.id = oi.prodID GROUP BY p.id, p.category ), RankedProducts AS ( -- 按类别分区,每个类别内按销量降序排名 SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) AS sales_rank FROM ProductSales ) -- 筛选每个类别排名前3的产品 SELECT id, category, sales FROM RankedProducts WHERE sales_rank <= 3 ORDER BY category, sales DESC;
方案2:保留并列排名的前三
如果需要保留销量并列的产品(比如两个产品都是第三,都要显示),把ROW_NUMBER()换成DENSE_RANK()即可:
WITH ProductSales AS ( SELECT p.id, p.category, ISNULL(SUM(oi.quantity), 0) AS sales FROM Products p LEFT JOIN OrderItems oi ON p.id = oi.prodID GROUP BY p.id, p.category ), RankedProducts AS ( SELECT *, DENSE_RANK() OVER (PARTITION BY category ORDER BY sales DESC) AS sales_rank FROM ProductSales ) SELECT id, category, sales FROM RankedProducts WHERE sales_rank <= 3 ORDER BY category, sales DESC;
运行这个查询,就能得到你预期的结果,同时还能处理无订单产品的销量问题,性能也比你的原SQL好很多。
内容的提问来源于stack exchange,提问作者John L.




