Django ORM按天统计记录时缺失日期自动补0的实现方法
这个需求两种实现方式都可行,你可以根据项目场景选择对应方案:
方案1:纯Python实现(无额外依赖,全数据库兼容)
适合数据量不大的场景,不需要引入pandas,也不需要写原生SQL,兼容性最好。
from datetime import timedelta, datetime from django.db.models import Count from django.db.models.functions import TruncDate # 先将起止字符串转为date类型,保证后续键匹配类型一致 start_date = datetime.strptime('2021-9-1', '%Y-%m-%d').date() end_date = datetime.strptime('2021-9-30', '%Y-%m-%d').date() # 原有ORM查询,拿到有数据的日期统计结果 date_counts = Tracking.objects.filter( scan_time__date__gte=start_date, scan_time__date__lte=end_date ).annotate( scanned_date=TruncDate('scan_time') ).values('scanned_date').annotate( total=Count('created') ).order_by('scanned_date') # 转为日期为键、统计数为值的字典,提高查询效率 count_map = {item['scanned_date']: item['total'] for item in date_counts} # 生成全量日期序列,遍历补0 full_result = [] current_date = start_date while current_date <= end_date: full_result.append({ 'scanned_date': current_date, 'total': count_map.get(current_date, 0) }) current_date += timedelta(days=1)
遍历full_result即可输出你需要的日期: 统计数格式内容。
方案2:Django ORM + 原生SQL实现(适合大数据量场景)
如果数据量很大,不想在Python层做循环,可以结合数据库的连续序列生成能力直接查询,以PostgreSQL为例:
from django.db import connection sql = """ SELECT series.date::date AS scanned_date, COALESCE(t.total, 0) AS total FROM generate_series(%s::date, %s::date, '1 day'::interval) AS series(date) LEFT JOIN ( SELECT DATE(scan_time) AS scan_date, COUNT(created) AS total FROM tracking WHERE DATE(scan_time) BETWEEN %s AND %s GROUP BY DATE(scan_time) ) t ON t.scan_date = series.date ORDER BY series.date """ with connection.cursor() as cursor: cursor.execute(sql, [start_date, end_date, start_date, end_date]) query_result = cursor.fetchall() # 转为字典格式 full_result = [{'scanned_date': item[0], 'total': item[1]} for item in query_result]
MySQL 8.0+ 也可以用递归CTE生成连续日期,替换对应SQL即可。
方案3:Pandas实现(适合需要后续数据分析的场景)
如果项目已经引入Pandas做报表分析,几行代码即可完成补0操作:
import pandas as pd # 把原有ORM查询结果转为DataFrame count_df = pd.DataFrame(date_counts) count_df['scanned_date'] = pd.to_datetime(count_df['scanned_date']).dt.date # 生成全量日期序列,重建索引补0 full_dates = pd.date_range(start=start_date, end=end_date, freq='D').date full_df = count_df.set_index('scanned_date').reindex(full_dates, fill_value=0).reset_index() full_df.rename(columns={'index': 'scanned_date'}, inplace=True) # 转为字典列表格式 full_result = full_df.to_dict('records')
内容的提问来源于stack exchange,提问作者Anuj TBE
相关产品推荐
相关产品推荐

