代码之家  ›  专栏  ›  技术社区  ›  Arne Claassen

对django orm中的外键表进行查询

  •  1
  • Arne Claassen  · 技术社区  · 7 年前

    鉴于:

    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公司 酒吧 ,我必须加入两次外键(如果我在写SQL)。在姜戈有办法做到这一点吗?

    我已经试过了 .filter(Q(tag__name='foo') & Q(tag__name='bar')) 但这只会造成不可能满足的查询 Tag 两者都有 FOO公司 酒吧 作为它的名字。

    1 回复  |  直到 7 年前
        1
  •  1
  •   willeM_ Van Onsem    7 年前

    这并不像看上去那么紧张。此外 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) 用于启用 视频 S 没有 标签。这可能看起来不必要,但如果 tag_list 是空的,因此我们要获得 全部的 视频(因为它们没有共同的标签)。

    我们还假设 Tag S是 独特的 否则,某些标记可能会计数两次。

    在窗帘后面,我们将执行如下查询:

    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