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

Django使用TruncHour统计时补全零计数小时的问题求助

Django + PostgreSQL 统计全天每小时事件数(缺失小时补0)

你当前的查询仅返回有事件的小时,是因为GROUP BY聚合只会处理表中存在的数据,不会自动生成不存在的时间区间。另外你原有查询的写法存在分组逻辑错误:annotate(event_count=Count('*')) 放在values之前不会按小时分组统计,需要调整顺序先指定分组字段再执行聚合,否则会得到错误的计数结果。

以下是两种可行的实现方案:

方案1:Python层补全(实现成本最低,24小时场景性能无损耗)

该方案无需修改数据库查询逻辑,仅对查询结果做后处理,代码简单易维护:

import datetime
from django.db.models import Count
from django.db.models.functions import TruncHour

# 修正后的原有统计查询,按小时分组统计
event_volume = IngestModels.EventData.objects.filter(
    product=my_product, created__date=selected_date
).annotate(
    hour=TruncHour('created')
).values("hour").annotate(
    event_count=Count('*')
)
# 把统计结果转成 小时:计数 的映射字典
event_count_map = {item['hour']: item['event_count'] for item in event_volume}

# 生成全天24小时的完整结果,缺失小时补0
full_result = []
start_dt = datetime.datetime.combine(selected_date, datetime.time(0,0,0))
for hour_offset in range(24):
    current_hour = start_dt + datetime.timedelta(hours=hour_offset)
    # 若项目开启时区支持,需给current_hour加上对应时区避免匹配错误
    full_result.append({
        "hour": current_hour,
        "event_count": event_count_map.get(current_hour, 0)
    })

方案2:PostgreSQL数据库层面实现(适合大数量/多时间区间聚合场景)

利用PostgreSQL内置的generate_series函数生成完整时间序列,再和统计结果左连接,直接在数据库层面完成补0,无需Python层后处理:

原生SQL实现(性能最优)

import datetime
from django.db import connection

start_dt = datetime.datetime.combine(selected_date, datetime.time(0,0,0))
end_dt = datetime.datetime.combine(selected_date, datetime.time(23,59,59))

with connection.cursor() as cursor:
    cursor.execute("""
        WITH all_hours AS (
            SELECT generate_series(%s, %s, INTERVAL '1 hour') AS hour
        )
        SELECT 
            all_hours.hour,
            COALESCE(COUNT(ed.id), 0) AS event_count
        FROM all_hours
        LEFT JOIN event_data ed 
            ON DATE_TRUNC('hour', ed.created) = all_hours.hour
            AND ed.product_id = %s
        GROUP BY all_hours.hour
        ORDER BY all_hours.hour
    """, [start_dt, end_dt, my_product.id])
    # 直接返回补0后的完整结果
    full_result = [{"hour": row[0], "event_count": row[1]} for row in cursor.fetchall()]

纯Django ORM实现(无需写原生SQL)

import datetime
from django.db.models import Count, F, OuterRef, Subquery, Coalesce, Value, DateTimeField
from django.db.models.functions import TruncHour

start_dt = datetime.datetime.combine(selected_date, datetime.time(0,0,0))
end_dt = datetime.datetime.combine(selected_date, datetime.time(23,59,59))

# 自定义ORM函数调用PG的generate_series
class GenerateSeries(Func):
    function = 'generate_series'
    output_field = DateTimeField()

# 生成全天24小时的基础查询集
all_hours_qs = GenerateSeries(
    Value(start_dt),
    Value(end_dt),
    Value('1 hour', output_field=DateTimeField())
).annotate(hour=F('generate_series')).values('hour')

# 事件统计子查询
event_subq = IngestModels.EventData.objects.filter(
    product=my_product,
    hour=OuterRef('hour')
).annotate(
    hour=TruncHour('created')
).values('hour').annotate(
    cnt=Count('*')
).values('cnt')

# 关联查询补0
full_result = all_hours_qs.annotate(
    event_count=Coalesce(Subquery(event_subq), 0)
).values('hour', 'event_count')

注意事项

如果你的Django项目开启了时区支持,需要确保生成的时间序列和created字段的时区一致,避免出现时间匹配错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 00:36:02