You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用无循环精简SQL按自定义年度日期区间GROUP BY汇总数据

自定义年度区间的SQL分组汇总方案

场景

需要按自定义日期区间进行数据分组汇总,常规GROUP BY年份无法满足需求。自定义年度区间定义为:上年12月20日00:00:00至当年12月19日23:59:59。

现有History表数据

id | date                | income  | spent  
--------------------------------------------
1  | 2019-12-21 17:15:00 | 600,00  | NULL
2  | 2019-12-23 12:55:00 | 183,00  | NULL
3  | 2019-12-30 20:05:00 | NULL    | 25,00
4  | 2020-01-01 15:35:00 | NULL    | 13,00
5  | 2020-01-01 20:25:00 | NULL    | 500,50
6  | 2020-12-10 10:25:00 | NULL    | 5,50
7  | 2021-05-22 12:45:00 | 1098,00 | NULL
8  | 2021-05-23 10:18:00 | NULL    | 186,00
9  | 2021-11-25 12:32:00 | NULL    | 10,00
10 | 2021-12-23 10:35:00 | NULL    | 10,00

预期汇总结果

Year | Summary Income | Summary Spent | Difference
--------------------------------------------------
2020 |         783,00 |        544,00 |     239,50
2021 |        1098,00 |        196,00 |     902,00
2022 |           0,00 |         10,00 |     -10,00

当前实现(存储过程循环)

...
SET @Aa = (SELECT MIN(date) FROM History);
CREATE TEMPORARY TABLE Yr (Year VARCHAR(4), Income FLOAT(8,2), Spent FLOAT(8,2), differ FLOAT(8,2));
Yearly: LOOP
SET @Aa = @Aa + 1;
SET @From = CONCAT((@Aa - 1), '-12-20 00:00:00');
SET @To = CONCAT(@Aa, '-12-19 23:59:59');
SET @Count = (SELECT SUM(income) FROM History WHERE date >= @From AND date <= @To);
SET @diff = (SELECT SUM(spent)  FROM History WHERE date >= @From AND date <= @To);
INSERT INTO Yr (Year, Income, Spent, differ) VALUES (@Aa, @Count, @diff, (@Count - @diff));
IF (@Aa = (SELECT MAX(YEAR(date)) FROM History)) THEN LEAVE Yearly; END IF;
END LOOP;
SELECT * FROM Yr;
...

问题

是否可以不使用循环,通过精简的SQL语句实现该自定义年度区间的汇总需求?


解决方案:无循环的SQL实现

可以通过日期偏移计算分组键的方式替代循环,核心思路是先判断每条记录所属的自定义年度区间,再直接分组汇总,同时生成完整年度序列确保无数据年度也能显示。

完整SQL语句

WITH year_range AS (
    -- 生成覆盖所有涉及自定义年度的序列
    SELECT MIN(custom_year) AS year_num
    FROM (
        SELECT CASE 
            WHEN MONTH(date) = 12 AND DAY(date) >= 20 THEN YEAR(date) + 1
            ELSE YEAR(date)
        END AS custom_year
        FROM History
    ) t
    UNION ALL
    SELECT year_num + 1
    FROM year_range
    WHERE year_num < (SELECT MAX(custom_year) FROM (
        SELECT CASE 
            WHEN MONTH(date) = 12 AND DAY(date) >= 20 THEN YEAR(date) + 1
            ELSE YEAR(date)
        END AS custom_year
        FROM History
    ) t)
),
history_summary AS (
    -- 按自定义年度分组汇总收支
    SELECT 
        CASE 
            WHEN MONTH(date) = 12 AND DAY(date) >= 20 THEN YEAR(date) + 1
            ELSE YEAR(date)
        END AS custom_year,
        COALESCE(SUM(REPLACE(income, ',', '.')), 0) AS summary_income,
        COALESCE(SUM(REPLACE(spent, ',', '.')), 0) AS summary_spent
    FROM History
    GROUP BY custom_year
)
-- 关联年度序列与汇总结果,格式化输出
SELECT 
    yr.year_num AS `Year`,
    FORMAT(hs.summary_income, 2, 'de_DE') AS `Summary Income`,
    FORMAT(hs.summary_spent, 2, 'de_DE') AS `Summary Spent`,
    FORMAT(hs.summary_income - hs.summary_spent, 2, 'de_DE') AS `Difference`
FROM year_range yr
LEFT JOIN history_summary hs ON yr.year_num = hs.custom_year
ORDER BY yr.year_num;

关键逻辑说明

  1. 自定义年度判断:用CASE语句识别每条记录所属的自定义年度——若日期在12月20日及以后,则归为下一年的区间,否则归为当年区间。
  2. 年度序列生成:通过递归CTEyear_range生成所有需要统计的年度,确保像2022这种只有单条数据的年度不会被遗漏。
  3. 数据格式处理:
    • REPLACE(income, ',', '.'):将原始数据的逗号小数点转换为标准格式,保证求和计算正确。
    • COALESCE(..., 0):把无数据的收支值替换为0,避免显示NULL。
    • FORMAT(..., 2, 'de_DE'):将结果格式化为带逗号小数点的样式,匹配预期输出格式。

内容的提问来源于stack exchange,提问作者colper89

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 02:24:32