簡體   English   中英

Django 查詢多對多子集包含

[英]Django query for many-to-many subset containment

有沒有辦法查詢多對多字段的子集或超集包含?

假設每個人都有一個他們想看的鳥的列表,每個鳥舍都有一個鳥的列表。 對於給定的 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

如果有一個查詢可以滿足我的要求,我也很想知道是否/為什么比“手動”執行此操作更可取。 例如,我可以循環遍歷鳥舍,提取每個鳥舍的鳥類列表,然后查看此人的 Birds_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

任何幫助表示贊賞!

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)過濾新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

使用 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 字段是什么。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM