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

MySQL技术问询:统计年度各月活跃事件数时出现重复结果

问题描述

我有一张存储事件历史记录的MySQL表,包含超1000万条数据,表结构如下:

id   start_date   end_date
1    2023-01-01   2023-04-05
2    2023-01-06   2023-02-01
3    2023-04-01   2024-01-05

需求是统计每年每个月份的活跃事件数量(例如2023年1月应返回2个活跃事件,2023年4月也应返回2个),且已拥有包含每日、每月、每年数据的日历表。

尝试了以下查询,但结果出现大量重复行:

SELECT
    calendar.calendar_month,
    calendar.calendar_year,
    COUNT(*) AS active_events_count
from calendars calendar
LEFT JOIN
    events AS e
ON
    calendar.calendar_date BETWEEN DATE_FORMAT(e.start_date, '%Y-%m-01') AND DATE_FORMAT(e.end_date, '%Y-%m-01')
GROUP BY
     calendar.calendar_year,   calendar.calendar_month
ORDER BY
    calendar.calendar_year,   calendar.calendar_month

补充示例:
事件数据:

Id  start_date   end_date
10  2023-01-01   2023-05-31

执行查询:

SELECT
    calendar.calendar_month,
    calendar.calendar_year,
    e.start_date,
    e.end_date,
    e.id
from calendars calendar
LEFT JOIN
    events e
ON
    calendar.calendar_date BETWEEN DATE_FORMAT(e.start_date, '%Y-%m-01') AND DATE_FORMAT(e.end_date + INTERVAL 1 MONTH, '%Y-%m-01') - INTERVAL 1 DAY
where
e.start_date >= '2023-01-01'
and e.id = 10
ORDER BY
    calendar.calendar_year,   calendar.calendar_month, id

得到重复结果:

Id  calendar_year   calendar_month  start_date   end_date
10  2023            1               2023-01-13   2023-01-13
(重复31次)
解决方案

问题根源

你的查询用日历表的每日数据和事件表关联,导致一个覆盖整月的事件会和该月的31条(或对应天数)日历记录一一匹配,最终每条事件在对应月份会被重复统计多次,这就是重复行的原因。

优化方案

方案1:利用日历表的月度数据(推荐)

如果日历表包含月度维度的记录(比如每条记录对应一个年月,有calendar_year、calendar_month,以及该月的起始/结束日期),直接用月度数据关联,避免每日匹配:

SELECT
    cal.calendar_year,
    cal.calendar_month,
    COUNT(DISTINCT e.id) AS active_events_count
FROM calendars_monthly cal
LEFT JOIN events e
    ON e.start_date <= LAST_DAY(cal.calendar_date)
    AND e.end_date >= DATE_FORMAT(cal.calendar_date, '%Y-%m-01')
GROUP BY cal.calendar_year, cal.calendar_month
ORDER BY cal.calendar_year, cal.calendar_month;

如果只有每日日历表,也可以先提取每月唯一记录再关联:

SELECT
    cal.calendar_year,
    cal.calendar_month,
    COUNT(DISTINCT e.id) AS active_events_count
FROM (
    SELECT DISTINCT calendar_year, calendar_month, DATE_FORMAT(calendar_date, '%Y-%m-01') AS month_start, LAST_DAY(calendar_date) AS month_end
    FROM calendars
) cal
LEFT JOIN events e
    ON e.start_date <= cal.month_end
    AND e.end_date >= cal.month_start
GROUP BY cal.calendar_year, cal.calendar_month
ORDER BY cal.calendar_year, cal.calendar_month;

方案2:在原查询中去重统计

如果必须使用日历表的每日数据,统计时用COUNT(DISTINCT e.id)代替COUNT(*),确保每个事件在对应月份只被统计一次:

SELECT
    calendar.calendar_year,
    calendar.calendar_month,
    COUNT(DISTINCT e.id) AS active_events_count
FROM calendars calendar
LEFT JOIN events e
    ON calendar.calendar_date BETWEEN e.start_date AND e.end_date
GROUP BY calendar.calendar_year, calendar.calendar_month
ORDER BY calendar.calendar_year, calendar.calendar_month;

注意:这个方法因为要处理每日数据关联,性能会比方案1差,对于1000万+数据的事件表,优先用方案1。

方案3:直接生成年月范围关联(无需日历表)

如果日历表只是用来生成年月维度,也可以直接用递归生成年月范围和事件表关联,避免依赖日历表:

WITH RECURSIVE year_months AS (
    SELECT '2023-01-01' AS month_start
    UNION ALL
    SELECT DATE_ADD(month_start, INTERVAL 1 MONTH)
    FROM year_months
    WHERE month_start <= '2024-12-01' -- 按需调整结束年月
)
SELECT
    YEAR(month_start) AS calendar_year,
    MONTH(month_start) AS calendar_month,
    COUNT(DISTINCT e.id) AS active_events_count
FROM year_months ym
LEFT JOIN events e
    ON e.start_date <= LAST_DAY(ym.month_start)
    AND e.end_date >= ym.month_start
GROUP BY YEAR(month_start), MONTH(month_start)
ORDER BY calendar_year, calendar_month;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 23:51:18