[英]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.