如何找到两个Django查询集的交集?

42

我有一个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_setdocumentation_set都是指其他模型上的ManyToManyField。)

有没有办法获取一个查询集或对象列表,该列表是由每个管理器方法返回的查询集的交集?


什么阻止你将每个管理器的两个过滤器函数组合起来? - S.Lott
你的意思是像 Model.objects.managerMethodOne().managerMethodTwo() 这样吗?但好像不起作用。也许我没有正确编写我的管理器方法? - Paul D. Waite
3
为什么不这样做:Model.objects.filter(this=that).filter(that=somethingelse)?这是指筛选函数本身。 - S.Lott
啊!是的 - 两种方法都使用了相当复杂的过滤器,它们都有注释。 - Paul D. Waite
1
@Paul D. Waite:您能提供一些关于这些管理器为什么如此复杂的指导吗?它们都支持get_query_set()吗?filter是否完全嵌入到get_query_set()方法中?是什么问题真正阻止了您简单地链接过滤器? - S.Lott
当然,我会把代码放上去。 - Paul D. Waite
8个回答

75
在大多数情况下,您只需编写以下代码(利用QuerySet的“Set”部分)::

(exploiting the "Set" part of QuerySet) :

intersection = Model.objects.filter(...) & Model.objects.filter(...)

这并没有很好地记录下来,但是它几乎像使用来自两个查询的条件的AND条件一样运行。相关代码:https://github.com/django/django/blob/1.8c1/django/db/models/query.py#L203


1
是的,我尝试过了,但好像没有起作用。我只是得到了一个包含所有对象的查询集,包括那些不在较大查询集中的对象。 - Paul D. Waite
你能做到以下操作吗:intersection = Model.objects.filter(...) & Model.objects.filter(...) 然后 return HttpResponse("%s" % intersection.query) 这样做可以更容易地弄清楚Django在将两个查询合并为一个时正在做什么。 - Jordan Reiter
你提到这应该在“大多数”情况下有效。能否请您澄清一下哪些情况下它不会起作用? - wlo

28

你只需像这样做:

intersection = queryset1 & queryset2

要执行联合操作,只需将&替换为|


1
谢谢,它有效!但在切片查询集中不起作用。 - Adiyat Mubarak

27

根据Django 1.11版本,现在提供了函数intersection()

>>> qs1.intersection(qs2, qs3)

5

我认为 qs1.filter(pk__in=qs2) 应该可以正常工作(通常情况下)。对于类似的情况,它似乎对我有效,这是合理的,并且生成的查询看起来很健康。(如果你的查询集之一使用 values() 不选择主键列或其他奇怪的东西,我可以相信它会出问题...)


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() ) )

啊!是的,那很聪明,我之前还在想我的设计可能有问题。 - Paul D. Waite
12
无论它是否解决了他的问题,它仍然没有回答如何找到两个查询集的交集,这是谷歌搜索“django查询集交集”的第一个链接。 - johannestaas

3

如果你想在Python中完成,而不是在数据库中:

intersection = set(queryset1) & set(queryset2)

问题在于,如果您在查询中使用不同的注释,由于添加的注释,对象可能会看起来不同...

@lqc的答案和你的一样吗(你确定查询在Python级别上被评估并相交)?我的意思是,根据Django的文档,只有一些特殊情况下查询才会被评估(https://docs.djangoproject.com/en/4.1/ref/models/querysets/#when-querysets-are-evaluated)。所以,如果我们想要更好的性能,我们真的应该使用像intersection()或union()这样的内置函数吗?还是说这并不重要? - Lord ST
我创建了一个新的问题,如果你能回答我的问题,我将非常感谢。问题链接在这里 -> https://dev59.com/ZMX5oIgBc1ULPQZF43nG - Lord ST

0

一种方法可能是使用Python的集合模块,只需执行交集操作:

创建一些在id = 5处重叠的查询集:

In [42]: first = Location.objects.filter(id__lt=6)
In [43]: last = Location.objects.filter(id__gt=4)

首先导入"sets"(会得到一个弃用警告...嗯...好吧)。现在构建并求交集 - 我们得到了一个元素的集合:

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]

这显然会对数据库进行两次查询,并返回查询集中的所有元素 - 更好的方法是在您的管理器上链接过滤器,这样应该能够在一个数据库查询中完成,并且在 SQL 层面上。我看不到 QuerySet.and/or(QuerySet) 方法。


1
永远不要使用“sets”;它已被弃用,内置的“set”(对于不可变的“frozenset”)更好。 - Chris Morgan

0
如果您只是想使用注释来根据计数是否为零进行过滤,那么可以尝试以下代码:
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 的查询并没有生效。它选择了与一个非标准文档有关的任何特性 - 而我想要选择与没有标准文档相关的任何特性。 - Paul D. Waite

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接