Django查询多对多子集包含

时间:2014-03-03 04:22:14

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

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

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

以下是我的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

如果有一个查询符合我的要求,我也有兴趣知道是否/为什么更好地“手动”这样做。例如,我可以循环鸟舍,提取每个鸟舍的鸟类列表,看看该人的bird_to_see是鸟舍鸟类列表的子集还是超集:

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

感谢任何帮助!

2 个答案:

答案 0 :(得分:1)

使用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字段来调整输出字段。

答案 1 :(得分:0)

对于 Django> = 2.0 ,存在一个不错的解决方案。可以用匹配的鸟的数量注释鸟舍,并过滤至少匹配一个鸟或所需数量的鸟舍。

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 <2.0将需要引用中介模型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