如何在Django中筛选同时包含指定两个name的email?
问题描述
假设存在如下Event模型数据:
| name | |
|---|---|
| A | u1@example.org |
| B | u1@example.org |
| B | u1@example.org |
| C | u2@example.org |
| B | u3@example.org |
| B | u3@example.org |
| A | u4@example.org |
| B | u4@example.org |
需求是找出所有**同时关联name为A和B**的email,预期结果为["u1@example.org", "u4@example.org"]。
当前使用的代码无法满足需求,会误将仅关联单个name的重复邮箱(如u3@example.org)纳入结果:
emails = [ e["email"] for e in models.Event.objects.filter(name__in=["A", "B"]) .values("email") .annotate(count=Count("id")) .order_by() .filter(count__gt=1) ]
问题根源
原代码用Count("id")统计的是该邮箱在A/B筛选条件下的总记录数,而非关联的不同name数量。比如u3@example.org有两条B记录,总记录数为2,满足count__gt=1,但实际上只关联了B,不符合需求。
解决方案
方案1:统计去重后的name数量
使用Count("name", distinct=True)统计每个邮箱关联的不同name数量,筛选数量等于2的邮箱:
emails = [ e["email"] for e in models.Event.objects.filter(name__in=["A", "B"]) .values("email") .annotate(unique_name_count=Count("name", distinct=True)) .order_by() .filter(unique_name_count=2) ]
distinct=True确保每个name只被计数一次,只有同时关联A和B的邮箱,unique_name_count才会等于2,完美排除单个name重复的情况。
方案2:取两次筛选的交集
分别找出关联A和关联B的邮箱集合,取两者的交集:
# 方法1:用集合交集 a_emails = models.Event.objects.filter(name="A").values_list("email", flat=True) b_emails = models.Event.objects.filter(name="B").values_list("email", flat=True) emails = list(set(a_emails) & set(b_emails)) # 方法2:用Django QuerySet的intersection(Django 1.11+支持) emails = list( models.Event.objects.filter(name="A").values_list("email", flat=True) .intersection( models.Event.objects.filter(name="B").values_list("email", flat=True) ) )
这种方法逻辑直观,直接筛选同时存在于两个结果集的邮箱,天然避免单个name的干扰。
方案3:分别统计A/B的存在情况
用Case和Count分别统计每个邮箱是否有A和B的记录,筛选两者都存在的邮箱:
from django.db.models import Case, When, IntegerField emails = [ e["email"] for e in models.Event.objects.values("email") .annotate( has_a=Count(Case(When(name="A", then=1), output_field=IntegerField())), has_b=Count(Case(When(name="B", then=1), output_field=IntegerField())) ) .filter(has_a__gt=0, has_b__gt=0) ]
通过Case标记符合条件的记录,再用Count统计数量,只要has_a和has_b都大于0,说明该邮箱同时关联了A和B。
内容的提问来源于stack exchange,提问作者Guillaume Vincent
相关产品推荐
相关产品推荐

