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

Django ORM .distinct()返回重复Event结果,如何解决?

解决Django中多关联模型查询重复记录问题

问题场景

我有一个Event模型表示活动,EventShow模型表示活动的举办日期和时间,两者通过外键关联,模型代码如下:

class Event(models.Model):
    name = models.CharField(max_length=32)
    # ... 其他字段

class EventShow(models.Model):
    event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='shows')
    start_date = models.DateTimeField()

想要获取至少有一场未来演出的活动列表,使用了以下ORM查询语句:

events = Event.objects.filter(shows__start_date__gt=now()).prefetch_related('shows').distinct().order_by('shows__start_date')

但实际执行后,若一个活动有多场符合条件的演出,查询结果中仍会出现重复的活动记录。查看生成的SQL语句如下:

SELECT DISTINCT "core_event"."id", "core_event"."name", "core_eventshow"."start_date" 
  FROM "core_event" 
    INNER JOIN "core_eventshow" ON ("core_event"."id" = "core_eventshow"."event_id") 
  WHERE ("core_eventshow"."start_date" > 2023-08-16 02:48:46.063093) 
  ORDER BY "core_eventshow"."start_date" ASC

开发环境为SQLite。

问题原因

当前查询的核心问题在于DISTINCT的作用范围:SQL中的DISTINCT会对Event表字段+EventShow.start_date的整行组合进行去重。如果一个活动有多条未来演出记录,不同的start_date会让每行数据变得唯一,导致同一个Event被多次返回。

另外,order_by('shows__start_date')会强制将关联表的start_date字段加入SELECT列表,进一步放大了这个问题——因为即使Event字段完全相同,只要start_date不同,就会被视为不同的行。

解决方案

方案1:指定仅按Event主键去重(Django 2.0+)

使用distinct('id')明确告诉Django仅按Event的主键进行去重,同时通过annotate获取活动的最早未来演出时间,用这个注解字段来排序,避免关联表字段干扰去重逻辑:

from django.db.models import Min

events = Event.objects.filter(shows__start_date__gt=now())\
    .annotate(first_future_show=Min('shows__start_date'))\
    .prefetch_related('shows')\
    .distinct('id')\
    .order_by('first_future_show')

方案2:通过子查询过滤,避免JOIN带来的重复

先通过子查询筛选出所有存在未来演出的EventID,再基于这些ID查询活动,天然避免重复;同时用Prefetch可以只预取该活动的未来演出记录,减少不必要的数据加载:

from django.db.models import Subquery, OuterRef
from django.db.models import Prefetch, Min

future_event_ids = EventShow.objects.filter(
    event_id=OuterRef('id'),
    start_date__gt=now()
).values('event_id')

events = Event.objects.filter(
    id__in=Subquery(future_event_ids)
).prefetch_related(
    Prefetch('shows', queryset=EventShow.objects.filter(start_date__gt=now()))
).annotate(first_future_show=Min('shows__start_date'))\
.order_by('first_future_show')

方案3:SQLite兼容方案(若方案1不生效)

SQLite对DISTINCT ON语法支持有限,可先通过values_list获取唯一的EventID集合,再查询这些活动:

from django.db.models import Prefetch, Min

event_ids = Event.objects.filter(shows__start_date__gt=now()).values_list('id', flat=True).distinct()
events = Event.objects.filter(id__in=event_ids)\
    .prefetch_related(Prefetch('shows', queryset=EventShow.objects.filter(start_date__gt=now())))\
    .annotate(first_future_show=Min('shows__start_date'))\
    .order_by('first_future_show')

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:45:23