我有以下三种模型:
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
和模型managed = False
创建模型,Track.objects.filter(trackhastagfoo__isnull=True)
用于查询。我认为这不是一个优雅也不可持续的解决方案,因为它涉及在我的迁移中添加Raw SQL来维持上述观点。
这只是一个例子,其中我们需要在额外条件下进行这种左连接,但事实是,我们在应用程序的更多部分都面临着这个问题。
非常感谢!
答案 0 :(得分:9)
如Django #29555中所述,您可以为此目的since Django 2.0使用FilteredRelation
。
Track.objects.annotate(
has_tag=FilteredRelation(
'trackhastag', condition=Q(trackhastag__tag=1)
),
).filter(
has_tag__isnull=True,
)
答案 1 :(得分:0)
使用过滤器比排除方法要好...因为排除才可以首先获取整个查询,而且比排除不想要的itens更好,而过滤器只获取您想要的内容,就像您所说的 Track.objects.filter (trackhastagfoo__isnull = True)比排除一个更好。
建议:正如穆罕默德所说,您试图手动建立一个ManyToMany关系,为什么不尝试使用ManyToManyField?更容易使用
也许这可以回答您的问题:Django Left Outer Join
答案 2 :(得分:0)
queryset Extras又如何呢?它们不会破坏ORM,并且可以进一步过滤(与RawSQL相比)
from django.db.models import Q
Track.objects.filter(
# work around to force left outer join
Q(trackhastag__isnull=True) | Q(trackhastag__isnull=False)
).extra(
# where parameters are “AND”ed to any other search criteria
# thus we need to account for NULL
where=[
'"app_trackhastag"."id" <> %s or "app_trackhastag"."id" is NULL'
],
params=[1],
)
产生这个有点复杂的查询:
SELECT "app_track"."id", "app_track"."title", "app_track"."artist"
FROM "app_track"
LEFT OUTER JOIN "app_trackhastag"
ON ("app_track"."id" = "app_trackhastag"."track_id")
WHERE (
("app_trackhastag"."id" IS NULL OR "app_trackhastag"."id" IS NOT NULL) AND
("app_trackhastag"."id" <> 1 or "app_trackhastag"."id" is NULL)
)
以下是使用queryset进行左外部联接的一种简单方法:
Track.objects.filter(trackhastag__isnull=True)
给出:
SELECT "app_track"."id", "app_track"."title", "app_track"."artist"
FROM "app_track"
LEFT OUTER JOIN "app_trackhastag"
ON ("app_track"."id" = "app_trackhastag"."track_id")
WHERE "app_trackhastag"."id" IS NULL
意识到第一步完成后(我们有一个左外部联接),我们可以利用 queryset's extra:
Track.objects.filter(
trackhastag__isnull=True
).extra(
where=['"app_trackhastag"."id" <> %s'],
params=[1],
)
给出:
SELECT "app_track"."id", "app_track"."title", "app_track"."artist"
FROM "app_track"
LEFT OUTER JOIN "app_trackhastag"
ON ("app_track"."id" = "app_trackhastag"."track_id")
WHERE (
"app_trackhastag"."id" IS NULL AND
("app_trackhastag"."id" <> 1)
)
解决extra
的局限性(将所有参数“与”所有其他搜索条件的所有 )以得出上述最终解决方案。
答案 3 :(得分:-1)
恩瑞克,为什么您没有使用多对多关系
class Track(models.Model):
title = models.TextField()
artist = models.TextField()
tags = models.ManyToManyField(Tag)
class Tag(models.Model):
name = models.CharField(max_length=50)
还有你的问题
Track.objects.filter(~Q(tags__id=1))