【问题标题】:How can I find the intersection of two Django querysets?如何找到两个 Django 查询集的交集?
【发布时间】: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() 方法中?阻止您简单地链接过滤器的真正问题是什么?

标签: python django django-models


【解决方案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)
      

      【讨论】:

        猜你喜欢
        • 2011-05-23
        • 2022-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-06
        • 1970-01-01
        • 2019-08-11
        • 1970-01-01
        相关资源
        最近更新 更多