AND 查询 Django ORM 中的外键 table

AND query against foreign key table in 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']) 找到所有具有 foo bar 标签的 Videos,但是为了找到具有 foo AND bar 的那些,我必须两次加入外键(如果我手写 SQL ).有没有办法在 Django 中完成此操作?

我已经尝试过 .filter(Q(tag__name='foo') & Q(tag__name='bar')) 但这只会创建无法满足查询的条件,其中单个 Tag 同时具有 foobar 作为其名称。

这并不像看起来那么简单。此外 JOIN 使用相同的 table 两次通常根本不是一个好主意:假设您的列表包含十个元素。你要JOIN十次吗?这很容易变得不可行。

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

tag_list = ['foo', 'bar']
tag_set = <b>set(</b>tag_list<b>)</b>

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

from django.db.models import <b>Q</b>

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

请注意,Q(tag__isnull-True) 用于启用 Videos 而没有 标签。这可能看起来没有必要,但如果 tag_list 为空,我们因此想要获得 all 个视频(因为它们的共同标签为零)。

我们还假设 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