Django ORM基于指定字段获取去重记录失败问题排查
问题原因分析
你的distinct('first_name', 'last_name', 'date_of_birth')无效,核心原因有两点:
union的去重逻辑:union默认会基于所有选定字段进行去重,只要任意一个字段值不同(比如你这里的source字段,一个是'booking'、一个是'patient'),就会被视为两条独立记录保留下来,所以即使三个标识字段相同,这两条记录在union后依然共存。distinct(*fields)的局限性:在ValuesQuerySet中使用distinct(*fields),并不会忽略其他字段的差异——它仅保证返回的记录中指定字段的组合唯一,但如果两条记录指定字段相同但其他字段不同,数据库无法确定返回哪一条,且无法满足你“优先保留source=patient”的需求。
解决方案
针对你的需求(当两个模型存在相同first_name/last_name/date_of_birth的记录时,保留source=patient的那条),推荐以下两种实现方式:
方案一:使用窗口函数(高效,支持大数据量)
利用Django的窗口函数RowNumber,按三个标识字段分组,将source='patient'的记录排在每组首位,最后筛选出每组的第一条记录即可。
修改后的代码:
from django.db.models import Window, F, Q, Value from django.db.models.functions import RowNumber from django.db.models import ExpressionWrapper, DateTimeField from datetime import datetime, timedelta def get_queryset(self): current_date = datetime.today() # 构建符合条件的booking数据集 booking_data = PatientBooking.objects.annotate( admission_datetime=F('requested_admission_datetime'), delivery_datetime=F('estimated_due_date'), fully_dilation_datetime=ExpressionWrapper( F('estimated_due_date') - timedelta(days=1), output_field=DateTimeField() ), source=Value('booking', output_field=CharField()) ).filter( status="ACCEPTED", admission_datetime__date=current_date ).values( 'first_name', 'last_name', 'delivery_type', 'date_of_birth', 'admission_datetime', 'delivery_datetime', 'fully_dilation_datetime', 'source' ) # 构建符合条件的patient数据集 patient_data = PatientDetails.objects.annotate( admission_datetime=F('created_at'), delivery_datetime=F('estimated_due_date'), fully_dilation_datetime=F('patient_fully_dilated'), source=Value('patient', output_field=CharField()) ).filter( status="ACTIVE", admission_datetime__date=current_date ).values( 'first_name', 'last_name', 'delivery_type', 'date_of_birth', 'admission_datetime', 'delivery_datetime', 'fully_dilation_datetime', 'source' ) # 合并两个数据集(all=True保留所有记录,包括字段重复的) combined = booking_data.union(patient_data, all=True) # 给每组相同标识的记录添加排名,source='patient'排第一 combined_with_rank = combined.annotate( row_num=Window( expression=RowNumber(), partition_by=['first_name', 'last_name', 'date_of_birth'], order_by=F('source').asc() # 'patient'字典序比'booking'靠前,因此排名为1 ) ) # 筛选每组第一条记录,即优先保留source=patient的记录 result = combined_with_rank.filter(row_num=1).order_by('first_name', 'last_name', 'date_of_birth') return result
方案二:先保留patient记录,再补充无重复的booking记录
逻辑更直观:先获取所有符合条件的patient记录,再筛选出那些不在patient记录中的booking记录,最后合并两者。
修改后的代码:
from django.db.models import F, Q, Value from django.db.models import ExpressionWrapper, DateTimeField from datetime import datetime, timedelta def get_queryset(self): current_date = datetime.today() # 获取符合条件的patient数据集 patient_data = PatientDetails.objects.annotate( admission_datetime=F('created_at'), delivery_datetime=F('estimated_due_date'), fully_dilation_datetime=F('patient_fully_dilated'), source=Value('patient', output_field=CharField()) ).filter( status="ACTIVE", admission_datetime__date=current_date ).values( 'first_name', 'last_name', 'delivery_type', 'date_of_birth', 'admission_datetime', 'delivery_datetime', 'fully_dilation_datetime', 'source' ) # 提取patient的三个标识字段组合,用于排除booking中的重复记录 patient_identifiers = patient_data.values_list('first_name', 'last_name', 'date_of_birth') # 获取无重复的booking数据集(排除已经在patient中存在的记录) booking_data = PatientBooking.objects.annotate( admission_datetime=F('requested_admission_datetime'), delivery_datetime=F('estimated_due_date'), fully_dilation_datetime=ExpressionWrapper( F('estimated_due_date') - timedelta(days=1), output_field=DateTimeField() ), source=Value('booking', output_field=CharField()) ).filter( status="ACCEPTED", admission_datetime__date=current_date, ~Q((F('first_name'), F('last_name'), F('date_of_birth'))__in=patient_identifiers) ).values( 'first_name', 'last_name', 'delivery_type', 'date_of_birth', 'admission_datetime', 'delivery_datetime', 'fully_dilation_datetime', 'source' ) # 合并patient和无重复的booking数据 combined_queryset = patient_data.union(booking_data).order_by('first_name', 'last_name', 'date_of_birth') return combined_queryset
内容的提问来源于stack exchange,提问作者sandeepsinghnegi
相关产品推荐
相关产品推荐

