AND查询django ORM中的外键表

时间:2018-06-12 19:28:25

标签: django django-orm

假设:

class Video(models.Model):
  tags = models.ManyToManyField(Tag)

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

我知道我可以使用Video.objects.filter(tags__name__in=['foo','bar'])查找包含Videos foo标记的所有bar,但为了找到这些标记有foo AND bar,我必须加入外键两次(如果我手写SQL)。有没有办法在Django中实现这个目标?

我已经尝试了.filter(Q(tag__name='foo') & Q(tag__name='bar')),但这只会创建一个不可能满足的查询,其中一个Tag同时包含foobar。< / p>

1 个答案:

答案 0 :(得分:1)

这不像它看起来那样直截了当。此外JOIN两次使用同一个表通常不是一个好主意:想象一下你的列表包含十个元素。你要去JOIN十次吗?这很容易变得不可行。

然而,我们可以做的是计算重叠。因此,如果给出一个元素列表,我们首先要确保这些元素是唯一的:

tag_list = ['foo', 'bar']
tag_set = set(tag_list)

接下来,我们计算实际在集合中的Video的标签数量,然后检查该数字是否与我们集合中的元素数量相同,如:

from django.db.models import Q

Video.objects.filter(
    Q(tag__name__in=tag_set) | Q(tag__isnull=True)
).annotate(
    overlap=Count('tag')
).filter(
    overlap=len(tag_set)
)

请注意,Q(tag__isnull-True)用于在没有标记的情况下启用Video s 。这可能看起来没必要,但如果tag_list为空,我们就想获得所有视频(因为它们共有零个标签)。

我们还假设Tag的名称是唯一,否则某些标签可能会被计算两次。

在幕后,我们将执行如下查询:

SELECT `video`.*, COUNT(`video_tag`.`tag_id`) AS overlap
FROM `video`
  LEFT JOIN `video_tag` ON `video_tag`.`video_id` = `video`.`id`
  LEFT JOIN `tag` ON `tag`.`id` = `video_tag`.`tag_id`
WHERE `tag`.`name` IN ('foo', 'bar')
GROUP BY `video`.`id`
HAVING overlap = 2