如何使用MySQL查询每个客户2016年消费最高的Top3商品
解决每个客户Top3消费商品的SQL问题
我明白你的问题啦——你当前的LIMIT 3是全局范围内取前3条记录,所以只会返回第一个客户的3条数据,而不是每个客户各自取消费金额最高的3个商品。下面给你两种实用的解决方案,适配主流SQL数据库:
方法一:使用窗口函数(推荐,简洁高效)
现在几乎所有主流数据库(MySQL 8.0+、PostgreSQL、SQL Server、Oracle等)都支持窗口函数,这是解决这类分组TopN问题最优雅的方式。
完整SQL代码
SELECT customer_name, product_name, total_sum FROM ( SELECT c.customer_name, p.product_name, s.total_sum, -- 按客户分组,组内按消费金额降序分配序号 ROW_NUMBER() OVER (PARTITION BY c.customer_id ORDER BY s.total_sum DESC) AS rank_num FROM sales s JOIN customers c ON c.customer_id = s.customer_id JOIN products p ON p.product_id = s.product_id -- 筛选2016年的销售记录 WHERE s.sale_date >= '2016-01-01' AND s.sale_date < '2017-01-01' ) ranked_sales -- 只保留每个客户的前3条记录 WHERE rank_num <= 3 -- 按要求排序:客户名升序,消费金额降序 ORDER BY customer_name ASC, total_sum DESC;
关键部分解释
PARTITION BY c.customer_id:将数据按客户分组,每个客户单独计算Top3ORDER BY s.total_sum DESC:在每个客户组内,按消费金额从高到低排序ROW_NUMBER():给每个组内的行分配唯一序号(1、2、3...),即使金额相同也会分配不同序号
可选调整:处理并列金额
如果需要允许并列排名(比如某个客户有2个商品金额相同且都是第2名),可以替换ROW_NUMBER()为:
RANK():相同金额的行分配相同序号,下一个不同金额的行序号会跳号(比如两个第1,下一个是第3)DENSE_RANK():相同金额的行分配相同序号,下一个不同金额的行序号连续(比如两个第1,下一个是第2)
方法二:关联子查询(适配老版本数据库)
如果你的数据库不支持窗口函数(比如MySQL 5.7及以前),可以用关联子查询实现:
完整SQL代码
SELECT c.customer_name, p.product_name, s.total_sum FROM sales s JOIN customers c ON c.customer_id = s.customer_id JOIN products p ON p.product_id = s.product_id WHERE s.sale_date >= '2016-01-01' AND s.sale_date < '2017-01-01' AND ( -- 统计当前客户2016年消费金额大于等于当前行的记录数 SELECT COUNT(*) FROM sales s2 WHERE s2.customer_id = s.customer_id AND s2.sale_date >= '2016-01-01' AND s2.sale_date < '2017-01-01' AND s2.total_sum >= s.total_sum ) <= 3 ORDER BY c.customer_name ASC, s.total_sum DESC;
逻辑说明
子查询会计算当前客户的所有2016年销售记录中,金额大于等于当前行的记录总数。如果这个总数≤3,说明当前行属于该客户的Top3消费商品。
内容的提问来源于stack exchange,提问作者Владимир Яшин
相关产品推荐
相关产品推荐

