PAYATHON 2026

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 加入分组。

备注:内容仅供参考。