在业务数据统计场景中,我们常常需要统计指定时间段内不同事件类别的发生次数,但默认的聚合查询会直接过滤掉没有对应事件的类别,导致统计结果缺失零计数的类别,影响数据的完整性。解决这个问题的核心思路是先准备完整的类别列表和时间范围,再通过左连接关联事件数据,最后完成聚合统计。

场景示例与基础表结构
假设我们有两个基础表,一个是事件类别表event_category,存储所有需要统计的事件类别;另一个是事件记录表event_record,存储实际发生的事件数据,包含事件类别ID、发生时间等字段。
表结构如下:
| 表名 | 字段名 | 字段说明 |
|---|---|---|
| event_category | category_id | 类别唯一ID |
| category_name | 类别名称 | |
| event_record | record_id | 事件记录ID |
| category_id | 关联的事件类别ID | |
| occur_time | 事件发生时间 |
核心实现步骤
1. 准备时间范围与完整类别列表
首先需要明确统计的时间段,同时获取所有需要统计的类别,作为左连接的主表,保证所有类别都会被保留。
2. 左连接事件数据并过滤时间条件
将完整类别列表作为左表,左连接事件记录表,注意时间过滤条件要放在左连接的ON子句中,而不是WHERE子句,否则会过滤掉没有事件的类别。
3. 分组聚合并统计计数
按照类别分组,使用COUNT函数统计事件记录数,没有匹配到事件的类别会返回0。
完整SQL代码示例
以下是统计2024年1月1日到2024年1月31日各事件类别发生次数的完整SQL代码:
-- 统计指定时间段内各事件类别的发生次数,包含零计数类别
SELECT
c.category_id,
c.category_name,
COUNT(r.record_id) AS event_count
FROM event_category c
-- 左连接事件记录表,时间条件放在ON子句中
LEFT JOIN event_record r
ON c.category_id = r.category_id
AND r.occur_time >= '2024-01-01 00:00:00'
AND r.occur_time <= '2024-01-31 23:59:59'
GROUP BY c.category_id, c.category_name
ORDER BY c.category_id;注意事项
- 时间过滤条件必须放在
LEFT JOIN的ON子句中,如果放在WHERE子句,左连接后没有匹配事件的记录会因为时间条件不满足被过滤,无法实现零计数保留。 - 聚合时建议使用事件记录表的主键(如
record_id)作为COUNT的参数,避免使用COUNT(*),因为COUNT(*)会把左连接产生的空行也计入,导致零计数类别的统计结果错误。 - 如果统计的时间段需要动态传入,可以将时间条件替换为对应的参数占位符,适配不同场景的统计需求。
这种实现方式适用于所有需要保留完整维度列表的聚合统计场景,不仅限于事件统计,也可以扩展到用户活跃统计、商品销量统计等同类需求中。