SQL Group by date:按日汇总 payment total
支付小周
结论
不要在 GROUP BY 中加入 pmttotamt。如果 [date] 包含时间,还需要先将它转换为纯日期再分组:
SELECT
CAST([date] AS date) AS payment_date,
SUM(pmttotamt) AS payment_total
FROM pmts
WHERE [date] >= '20250101'
AND [date] < '20250201'
GROUP BY CAST([date] AS date)
ORDER BY payment_date;
查询结果中,每个有付款记录的日期只有一行,payment_total 是当天收到的付款总额。
为什么会返回 518 行
问题出在原查询的分组条件:
GROUP BY [date], pmttotamt
把 pmttotamt 加入 GROUP BY 后,同一天内付款金额不同的记录会进入不同的分组,因此无法计算当天的总额。
如果 [date] 是 datetime 或 datetime2 类型,它还包含时、分、秒。日期相同但时间不同的记录也会被分开。例如:
2025-01-01 09:15:00
2025-01-01 14:30:00
直接按 [date] 分组时,这两条记录属于两个组。SELECT 中的 CAST 只会改变查询结果的显示方式,不会改变 GROUP BY 实际使用的值。
正确的分组方式
在 SQL Server 中,可以用 CAST([date] AS date) 去掉时间部分:
SELECT
CAST([date] AS date) AS payment_date,
SUM(pmttotamt) AS payment_total
FROM pmts
WHERE [date] >= '20250101'
AND [date] < '20250201'
GROUP BY CAST([date] AS date)
ORDER BY CAST([date] AS date);
日期筛选采用月初作为下限、下月月初作为上限的范围条件,比下面的写法更合适:
WHERE YEAR([date]) = 2025
AND MONTH([date]) = 1
对列调用 YEAR() 和 MONTH(),可能让数据库难以有效利用 [date] 上的索引。范围查询通常更利于索引查找,也能完整覆盖整个月份。
如果只需要有付款记录的日期
前面的查询已经符合要求。如果 1 月只有 20 天发生了付款,结果就只返回 20 行,不会固定返回 31 行。
如果必须返回 1 月的全部 31 天
GROUP BY 不会生成表中不存在的日期。如果没有付款的日期也要显示,并将金额设为 0,需要先生成日期序列,再通过 LEFT JOIN 连接付款汇总结果。
以下写法适用于支持递归 CTE 的 SQL Server:
WITH dates AS (
SELECT CAST('20250101' AS date) AS payment_date
UNION ALL
SELECT DATEADD(day, 1, payment_date)
FROM dates
WHERE payment_date < CAST('20250131' AS date)
),
daily_payments AS (
SELECT
CAST([date] AS date) AS payment_date,
SUM(pmttotamt) AS payment_total
FROM pmts
WHERE [date] >= '20250101'
AND [date] < '20250201'
GROUP BY CAST([date] AS date)
)
SELECT
d.payment_date,
COALESCE(p.payment_total, 0) AS payment_total
FROM dates AS d
LEFT JOIN daily_payments AS p
ON p.payment_date = d.payment_date
ORDER BY d.payment_date;
注意事项
[date]可能与数据库关键字或类型名称冲突。在 SQL Server 中应保留方括号。长期使用时,可以考虑将其改为payment_date或payment_datetime等含义更明确的列名。- 不建议使用
CAST([date] AS char(11))进行日期分组或排序。字符格式可能受数据库设置影响,字符串排序也未必符合日期顺序。 SUM(pmttotamt)会忽略NULL。如果某天的金额全部为NULL,汇总结果也可能是NULL。可以根据业务需要改用SUM(COALESCE(pmttotamt, 0))。- 如果
[date]已经是date类型且不含时间,可以直接使用GROUP BY [date],但仍不能把pmttotamt加入分组。
备注:内容仅供参考。
