【问题标题】:Django query for many-to-many subset containmentDjango查询多对多子集包含
【发布时间】:2014-04-04 00:29:45
【问题描述】:

有没有办法查询包含多对多字段的子集或超集?

假设每个人都有一个他们想看的鸟的列表,每个鸟舍都有一个鸟的列表。对于给定的 Person 实例,我如何进行查询以查找该人的列表中有每只鸟 的 Aviaries?同样,对于给定的 Person 实例,我如何找到哪些 Aviaries 在此人的列表中只有只鸟类(但不一定是所有鸟类)。

这是我的 Django 1.5 模型:

class Bird(models.Model):
    name = models.CharField(max_length=255, unique=True)

class Aviary(models.Model):
    name = models.CharField(max_length=255, unique=True)
    birds = models.ManyToManyField(Bird)

class Person(models.Model):
    name = models.CharField(max_length=255, unique=True)
    birds_to_see = models.ManyToManyField(Bird)

我知道我将如何找到至少有一个人的鸟类的鸟舍,但我不知道我将如何适应这里。 (例如,参见: django queryset for many-to-many field)

如果有一个查询可以执行我想要的操作,我也很想知道是否/为什么更“手动”执行此操作更可取。例如,我可以遍历 aviaries,提取每个 aviary 的鸟类列表,然后查看此人的 bird_to_see 是 aviary 鸟类列表的子集还是超集:

def find_aviaries(self):
    person_birds = set(self.birds_to_see.all())
    found_aviaries = []
    for aviary in Aviary.objects.all():
        aviary_birds = set(aviary.birds.all())
        if person_birds.issubset(aviary_birds):
            found_aviaries.append(aviary)            
    return found_aviaries

感谢任何帮助!

【问题讨论】:

    标签: python django many-to-many django-queryset django-orm


    【解决方案1】:

    Django >= 2.0 存在一个很好的解决方案。可以通过匹配鸟类的数量来注释 Aviaries,并过滤与至少一个 Bird 或所需数量匹配的 Aviaries。

    from django.db.models import Count
    
        ...
        person_birds = set(self.birds_to_see.all())
        aviaries = (
            Aviary.objects
            .annotate(bird_match_count=Count('birds', filter=Q(birds__in=person_birds)))
            .filter(bird_match_count__gt=0)
        )
    

    然后通过bird_match_count=len(person_birds) 过滤新查询集或在Python 中过滤原始查询集或按bird_match_count 对其进行排序是很简单的。

    Django AviaryBirds 并且会更加冗长。


    已通过读取 SQL 验证

    >>> print(aviaries.query)
    SELECT aviary.id, aviary.name,
      COUNT(CASE WHEN  aviary_birds.bird_id IN (1,..)  THEN aviary_birds.bird_id ELSE NULL END)
        AS bird_match_count
    FROM aviary LEFT OUTER JOIN aviary_birds ON (aviary.id = aviary_birds.aviary_id)
    GROUP BY aviary.id, aviary.name
    HAVING
      COUNT(CASE WHEN (aviary_birds.bird_id IN (1,..)) THEN aviary_birds.bird_id ELSE NULL END)
         > 0
    

    【讨论】:

    • 您应该能够引用Aviary.birds.through 以获取对旧版本 Django 中“通过”模型的引用。
    • @MatthewSchinckel 是的。我在关于 Django
    【解决方案2】:

    使用 Postgres 子查询数组构造,您可以对 id 进行注释,然后进行相应的过滤:

    birds = Aviary.birds.through.objects.filter(
        aviary=OuterRef('pk')
    ).values('bird')
    aviaries = Aviary.objects.annotate(
        bird_ids=SubqueryArray(birds)
    ).filter(bird_ids__contains=target_bird_ids)
    

    您也可以使用__contained_by 换另一条路(如果您只想匹配,也可以使用__overlap)。

    那么你只需要一个合适的SubqueryArray 类:

    class SubqueryArray(django.db.models.expressions.Subquery):
        template = 'ARRAY(%(subquery)s)'
        output_field = ArrayField(base_field=models.CharField())
    

    您可能需要调整其输出字段,具体取决于您的 PK 字段。

    【讨论】:

    • (后人注意:在提出原始问题时,此解决方案并不存在 - 该功能直到很久以后才实现)。
    猜你喜欢
    • 2011-11-29
    • 2021-11-14
    • 2011-07-20
    • 1970-01-01
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-28
    相关资源
    最近更新 更多