如何基于共同字段month合并两个Django QuerySet
问题描述
我有两个Django QuerySet,均包含month字段,分别用于统计过去6个月的每月新患者注册数和每月就诊数。现有代码如下:
six_months_ago = date.today() + relativedelta(months=-6) ###### 获取过去6个月每月新患者注册数 patient_per_month = patientprofile.objects.filter(pat_Registration_Date__gte=six_months_ago)\ .annotate( month=Trunc('pat_Registration_Date', 'month') ) \ .order_by('month') \ .values('month') \ .annotate(patient=Count('month')) print(patient_per_month) ###### 获取过去6个月每月就诊数 visits_per_month = visits.objects.filter(visit_date__gte=six_months_ago)\ .annotate( month=Trunc('visit_date', 'month') ) \ .order_by('month') \ .values('month') \ .annotate(visits=Count('month')) print(visits_per_month)
两个QuerySet输出结果:
<QuerySet [{'month': datetime.date(2022, 2, 1), 'patient': 1}, {'month': datetime.date(2022, 3, 1), 'patient': 1}, {'month': datetime.date(2022, 4, 1), 'patient': 1}, {'month': datetime.date(2022, 5, 1), 'patient': 2}, {'month': datetime.date(2022, 6, 1), 'patient': 2}, {'month': datetime.date(2022, 7, 1), 'patient': 18}]> <QuerySet [{'month': datetime.date(2022, 5, 1), 'visits': 6}, {'month': datetime.date(2022, 6, 1), 'visits': 7}, {'month': datetime.date(2022, 7, 1), 'visits': 12}]>
需要基于month字段合并两个QuerySet,缺失的数值以0填充,得到如下格式的结果:
<QuerySet [{'month': datetime.date(2022, 2, 1), 'patient': 1, 'visits': 0}, {'month': datetime.date(2022, 3, 1), 'patient': 1, 'visits': 0}, {'month': datetime.date(2022, 4, 1), 'patient': 1, 'visits': 0}, {'month': datetime.date(2022, 5, 1), 'patient': 2, 'visits': 6}, {'month': datetime.date(2022, 6, 1), 'patient': 2, 'visits': 7}, {'month': datetime.date(2022, 7, 1), 'patient': 18, 'visits': 12}]>
解决方案
方法一:Python层面合并(简单易实现)
将两个QuerySet转换为字典,以month为键存储统计值,再遍历所有存在的月份生成合并数据:
# 转换QuerySet为字典,快速查找对应月份的统计值 patient_map = {item['month']: item['patient'] for item in patient_per_month} visits_map = {item['month']: item['visits'] for item in visits_per_month} # 获取所有涉及的月份并排序 all_months = sorted(set(patient_map.keys()).union(visits_map.keys())) # 生成合并后的结果列表 merged_result = [] for month in all_months: merged_result.append({ 'month': month, 'patient': patient_map.get(month, 0), 'visits': visits_map.get(month, 0) })
如果需要严格覆盖过去6个月的所有月份(即使当月无数据),可以先生成完整的月份列表:
from datetime import date from dateutil.relativedelta import relativedelta # 生成过去6个月的每月1号日期 current_month = date.today().replace(day=1) all_months = [] for i in range(6): all_months.append(current_month - relativedelta(months=i)) all_months.sort() # 生成合并数据 merged_result = [] for month in all_months: merged_result.append({ 'month': month, 'patient': patient_map.get(month, 0), 'visits': visits_map.get(month, 0) })
方法二:数据库层面合并(大数据量更高效)
通过Django的Subquery和Coalesce在数据库层完成合并,减少内存占用:
from django.db.models import OuterRef, Subquery, IntegerField from django.db.models.functions import Coalesce # 定义就诊数的子查询 visits_subquery = visits.objects.filter( visit_date__gte=six_months_ago, month=OuterRef('month') ).annotate( month=Trunc('visit_date', 'month') ).values('month').annotate(visits_count=Count('month')).values('visits_count')[:1] # 合并患者统计与就诊统计,用Coalesce将NULL替换为0 merged_queryset = patientprofile.objects.filter(pat_Registration_Date__gte=six_months_ago)\ .annotate( month=Trunc('pat_Registration_Date', 'month') ).order_by('month')\ .values('month')\ .annotate( patient=Count('month'), visits=Subquery(visits_subquery, output_field=IntegerField()) ).annotate( visits=Coalesce('visits', 0) )
内容的提问来源于stack exchange,提问作者Saravanan Ragavan
相关产品推荐
相关产品推荐

