首页 > 解决方案 > 如何在 Django 中使用额外条件离开外部连接

问题描述

我有这三个模型:

class Track(models.Model):
    title = models.TextField()
    artist = models.TextField()

class Tag(models.Model):
    name = models.CharField(max_length=50)

class TrackHasTag(models.Model):
    track = models.ForeignKey('Track', on_delete=models.CASCADE)
    tag = models.ForeignKey('Tag', on_delete=models.PROTECT)

我想检索所有未使用特定标签标记的曲目。这让我得到了我想要Track.objects.exclude(trackhastag__tag_id='1').only('id')的:但是当表格增长时它非常慢。这是我在打印查询集时得到.query的:

SELECT "track"."id" 
FROM   "track" 
WHERE  NOT ( "track"."id" IN (SELECT U1."track_id" AS Col1 
                              FROM   "trackhastag" U1 
                              WHERE  U1."tag_id" = 1) )

我希望 Django 改为发送此查询:

SELECT "track"."id" 
FROM   "track" 
       LEFT OUTER JOIN "trackhastag" 
                    ON "track"."id" = "trackhastag"."track_id" 
                       AND "trackhastag"."tag_id" = 1 
WHERE  "trackhastag"."id" IS NULL; 

但是还没有找到这样做的方法。使用原始查询并不是一个真正的选择,因为我必须经常过滤结果查询集。

我发现的最干净的解决方法是在数据库中创建一个视图和一个用于查询的模型TrackHasTagFoo,例如: . 我不认为这是一个优雅且可持续的解决方案,因为它涉及将 Raw SQL 添加到我的迁移中以维护所述视图。managed = FalseTrack.objects.filter(trackhastagfoo__isnull=True)

这只是我们需要使用额外条件执行这种左连接的情况的一个示例,但事实是我们在应用程序的更多部分中都面临着这个问题。

非常感谢!

标签: djangodjango-models

解决方案


Django #29555中所述,您可以从 Django 2.0 开始FilteredRelation用于此目的。

Track.objects.annotate(
    has_tag=FilteredRelation(
        'trackhastag', condition=Q(trackhastag__tag=1)
    ),
).filter(
    has_tag__isnull=True,
)

推荐阅读