MySQL分组查询:求和count列并取最小expiry_date及对应id
解决MySQL分组统计并获取最小expiry_date对应id的问题
你的原查询之所以不符合预期,核心问题在于**GROUP BY package_id后,select列表里的mt.id和mt.expiry_date没有使用聚合函数**。在MySQL的非严格SQL模式下,这两个字段会返回分组内任意一行的值,而不是你想要的对应最小expiry_date的id和最小日期本身。另外你加的LEFT JOIN逻辑也没有起到筛选最小日期的作用。
结合你的需求——按package_id分组,统计count总和,同时获取该分组的最小expiry_date(或null)以及对应这个日期的id,我给你两种可行的解决方案:
方案一:使用窗口函数(MySQL 8.0及以上版本推荐)
窗口函数可以在不分组的前提下计算分组统计值,同时给每个分组内的行排序,方便我们筛选出对应最小日期的行:
WITH package_stats AS ( SELECT id, package_id, count, expiry_date, -- 计算当前package_id下的count总和 SUM(count) OVER (PARTITION BY package_id) AS total_count, -- 给每个package_id下的行排序:先排非null的日期,再按日期升序,最小日期的行rn=1 ROW_NUMBER() OVER ( PARTITION BY package_id ORDER BY CASE WHEN expiry_date IS NULL THEN 1 ELSE 0 END, expiry_date ASC ) AS row_rank FROM my_table ) SELECT id, package_id, total_count, expiry_date FROM package_stats WHERE row_rank = 1;
这个查询的逻辑是:
- 用
SUM(count) OVER (PARTITION BY package_id)计算每个分组的总count,不用提前分组 - 用
ROW_NUMBER()给每个分组内的行排序:把null日期排到最后,非null日期按升序排列,这样每个分组里row_rank=1的就是你要的最小expiry_date(或null)的行 - 最后筛选出每个分组的第一行,得到正确的id、总count和最小日期
方案二:子查询+关联(兼容MySQL 5.x版本)
如果你的MySQL版本不支持窗口函数,可以用子查询先算出每个分组的总count和最小日期,再关联原表找到对应id:
SELECT -- 找到对应最小expiry_date的id(如果有多个行日期相同,LIMIT 1取第一个) (SELECT id FROM my_table WHERE package_id = agg.package_id AND (expiry_date = agg.min_expiry OR (agg.min_expiry IS NULL AND expiry_date IS NULL)) LIMIT 1) AS id, agg.package_id, agg.total_count, agg.min_expiry AS expiry_date FROM ( -- 先按package_id分组,计算总count和最小expiry_date SELECT package_id, SUM(count) AS total_count, MIN(expiry_date) AS min_expiry FROM my_table GROUP BY package_id ) AS agg;
这个方案的逻辑是:
- 内层子查询
agg先完成分组统计,得到每个package_id的总count和最小expiry_date - 外层查询通过关联,找到每个package_id下对应最小日期(或null)的id
- 如果同一分组内有多个行的expiry_date都是最小值,
LIMIT 1会返回其中第一个id,你可以根据需求调整排序规则
这样就能得到你想要的结果:第一行id为2,expiry_date为2010-01-01 00:00:00,同时count总和正确。
内容的提问来源于stack exchange,提问作者MDaniyal
相关产品推荐
相关产品推荐

