如何找到两个 Django 查询集的交集?
Posted
技术标签:
【中文标题】如何找到两个 Django 查询集的交集?【英文标题】:How can I find the intersection of two Django querysets? 【发布时间】:2011-05-23 14:39:54 【问题描述】:我有一个带有两个自定义管理器方法的 Django 模型。每个都根据对象的不同属性返回模型对象的不同子集。
class FeatureManager(models.Manager):
def without_test_cases(self):
return self.get_query_set().annotate(num_test_cases=models.Count('testcase_set')).filter(num_test_cases=0)
def standardised(self):
return self.get_query_set().annotate(standardised=Count('documentation_set__standard')).filter(standardised__gt=0)
(testcase_set
和documentation_set
在其他型号上均指ManyToManyField
s。)
有没有办法获得一个查询集,或者只是一个对象列表,这是每个管理器方法返回的查询集的交集?
【问题讨论】:
是什么阻止您结合每个经理的两个过滤功能? 你的意思是像Model.objects.managerMethodOne().managerMethodTwo()
?那似乎不起作用。也许我没有正确编写我的管理器方法?
过滤器自己运行。 Model.objects.filter(this=that).filter(that=somethingelse)
。你为什么不这样做?
啊!是的——这两种方法都使用了某种复杂的过滤器,它们都有注释。
@Paul D. Waite:您能否就这些管理器如此复杂的原因提供一些指导?他们都支持get_query_set()
吗? filter
是否完全隐藏在 get_query_set()
方法中?阻止您简单地链接过滤器的真正问题是什么?
【参考方案1】:
一种方法可能是使用 python sets 模块并只做一个交集:
创建几个在 id=5 重叠的查询集:
In [42]: first = Location.objects.filter(id__lt=6)
In [43]: last = Location.objects.filter(id__gt=4)
首先“导入集”(收到弃用警告......嗯......哦,好吧)。现在构建它们并将它们相交 - 我们得到集合中的一个元素:
In [44]: sets.Set(first).intersection(sets.Set(last))
Out[44]: Set([<Location: Location object>])
现在获取交集元素的 id 来检查它是否真的是 5:
In [48]: [s.id for s in sets.Set(first).intersection(sets.Set(last))]
Out[48]: [5]
这显然会命中数据库两次并返回查询集的所有元素 - 更好的方法是将过滤器链接到您的管理器上,并且应该能够在一次 DB 命中和 SQL 级别上执行此操作。我看不到 QuerySet.and/or(QuerySet) 方法。
【讨论】:
永远不要使用sets
;它已被弃用,内置 set
(frozenset
表示不可变)更好。【参考方案2】:
在大多数情况下,您只需编写(利用 QuerySet 的“Set”部分):
intersection = Model.objects.filter(...) & Model.objects.filter(...)
这并没有很好的记录,但应该表现得几乎与在两个查询的条件上使用 AND 条件完全一样。相关代码:https://github.com/django/django/blob/1.8c1/django/db/models/query.py#L203
【讨论】:
是的,我试过了,但似乎没有用。我似乎只是从较小的查询集中获得了一个包含所有对象的查询集,包括那些不在较大查询集中的对象。 你能做到以下几点:intersection = Model.objects.filter(...) & Model.objects.filter(...)
然后return HttpResponse("%s" % intersection.query)
这样可以更容易地弄清楚 Django 将两个查询合并为一个时在做什么。【参考方案3】:
如果你想在 python 中做,而不是在数据库中:
intersection = set(queryset1) & set(queryset2)
问题是,如果您在查询中使用不同的注释,由于添加了注释,对象可能看起来不同......
【讨论】:
【参考方案4】:重构
class FeatureManager(models.Manager):
@staticmethod
def _test_cases_eq_0( qs ):
return qs.annotate( num_test_cases=models.Count('testcase_set') ).filter(num_test_cases=0)
@staticmethod
def _standardized_gt_0( qs ):
return qs.annotate( standardised=Count('documentation_set__standard') ).filter(standardised__gt=0)
def without_test_cases(self):
return self._test_cases_eq_0( self.get_query_set() )
def standardised(self):
return self._standardized_gt_0( self.get_query_set() )
def intersection( self ):
return self._test_cases_eq_0( self._standardized_gt_0( self.get_query_set() ) )
【讨论】:
啊!是的,这很聪明,我在想我的设计可能是问题所在。 无论它是否解决了他的问题,它仍然没有回答如何找到两个查询集的交集,这是谷歌在搜索“django 查询集的交集”时返回的第一个链接"【参考方案5】:如果您真的只是使用注释来根据计数是否为零进行过滤,那么这应该可以代替:
class FeatureManager(models.Manager):
def without_test_cases(self):
return self.get_query_set().filter(testcase__pk__isnull=True)
def standardised(self):
return self.get_query_set().filter(documentation_set__standard__isnull=False)
由于您不再担心注释,因此两个查询应该非常顺利地相交。
【讨论】:
啊,看,我不认为standardised
的查询有效。这会选择具有 one 相关文档但 不是 标准的任何功能 - 而我希望它选择具有 no 相关文档的任何功能是标准。【参考方案6】:
我相信 qs1.filter(pk__in=qs2) 应该(通常)工作。它似乎对我来说适用于类似的情况,它会起作用是有道理的,并且生成的查询看起来很理智。 (如果您的查询集之一使用 values() 来不选择主键列或其他奇怪的东西,我可以相信它会中断......)
【讨论】:
【参考方案7】:你可以这样做:
intersection = queryset1 & queryset2
要进行联合,只需将 &
替换为 |
【讨论】:
谢谢,它有效!但它在切片查询集中不起作用【参考方案8】:根据 Django 1.11,现在可以使用函数intersection()
>>> qs1.intersection(qs2, qs3)
【讨论】:
以上是关于如何找到两个 Django 查询集的交集?的主要内容,如果未能解决你的问题,请参考以下文章