如何找到两个 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_setdocumentation_set 在其他型号上均指ManyToManyFields。)

有没有办法获得一个查询集,或者只是一个对象列表,这是每个管理器方法返回的查询集的交集?

【问题讨论】:

是什么阻止您结合每个经理的两个过滤功能? 你的意思是像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;它已被弃用,内置 setfrozenset 表示不可变)更好。【参考方案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(...) &amp; 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

要进行联合,只需将 &amp; 替换为 |

【讨论】:

谢谢,它有效!但它在切片查询集中不起作用【参考方案8】:

根据 Django 1.11,现在可以使用函数intersection()

>>> qs1.intersection(qs2, qs3)

【讨论】:

以上是关于如何找到两个 Django 查询集的交集?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 中过滤查询集的多对多

oracle中结果集的交集并集差集组合查询

如何使用 Django 中另一个查询集的结果过滤查询集?

如何比较 Django 模板中的两个查询集?

如何访问 Django 中查询集的特定字段?

在 django 中获取空查询集的类名