【问题标题】:Django - Optimal way to sort models by boolean operationDjango - 通过布尔运算对模型进行排序的最佳方法
【发布时间】:2018-03-14 08:56:42
【问题描述】:

假设我有这个模型,我想通过逻辑操作对它们进行排序n1 != n2

class Thing(Model):
    n1 = IntegerField()
    n2 = IntegerField()
    ...

    def is_different(self):
        return self.n1 != self.n2

如果我按照sorted内置函数对它们进行排序,我发现它返回的不是Queryset,而是一个列表:

things = Thing.objects.all()    
sorted_things = sorted(things, key=lambda x: x.is_different())

现在,如果我使用annotate

sorted_things = things.annotate(diff=(F('n1') != F('n2'))).order_by('diff')

它会引发以下错误:AttributeError: 'bool' object has no attribute 'resolve_expression'

我找到了使用extra 查询集的解决方案:

sorted_things = things.extra(select={'diff': 'n1!=n2'}).order_by('diff')

但遵循 Django 文档 (https://docs.djangoproject.com/en/2.0/ref/models/querysets/#extra):

将此方法作为最后的手段

这是一个旧的 API,我们打算在未来的某个时候弃用它。仅当您无法使用其他查询集方法表达您的查询时才使用它。如果您确实需要使用它,请在您的用例中使用 QuerySet.extra 关键字提交一个工单(请先检查现有工单列表),以便我们可以增强 QuerySet API 以允许删除 extra()。我们不再改进或修复此方法的错误。

那么,最好的方法是什么?

谢谢!

【问题讨论】:

    标签: python django django-queryset


    【解决方案1】:

    条件表达式

    一种选择是使用conditional expressions。它们提供了检查条件并根据它们提供值之一的简单方法。在您的情况下,它看起来像:

    sorted_things = things.annotate(diff=Case(When(n1=F('n2'), then=True), default=False, output_field=BooleanField())).order_by('diff')
    

    Q 和 ExpressionWrapper

    还有另一种有点老套的方法,通过结合使用 QExpressionWrapper 来实现。

    在 django 中,Q 旨在用于filter()exclude()Case 等内部,但它只是创建了显然可以在任何地方使用的条件。它只有一个缺点:它没有定义输出的类型(它总是布尔值,django 可以假设在任何情况下都打算使用Q

    但是ExpressionWrapper 允许您包装任何表达式并定义它的最终输出类型。这样我们就可以简单地包装Q 表达式(或使用&| 和括号将多个Q 表达式粘合在一起)并手动定义它输出的类型。

    请注意,这是未记录的,因此这种行为将来可能会改变,但我已经使用 django 版本 1.8、1.11 和 2.0 对其进行了检查,并且工作正常

    例子:

    sorted_things = things.annotate(diff=ExpressionWrapper(Q(n1=F('n2')), output_field=BooleanField())).order_by('diff')
    

    【讨论】:

    • 条件表达式很好。谢谢!
    【解决方案2】:

    您可以使用Func() expressions 解决它。

    from django.db.models import Func, F
    
    class NotEqual(Func):
        arg_joiner = '<>'
        arity = 2
        function = ''
    
    things = Thing.objects.annotate(diff=NotEqual(F('n1'), F('n2'))).order_by('diff')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-07
      • 1970-01-01
      • 1970-01-01
      • 2011-05-15
      • 2017-05-23
      • 2021-01-04
      • 2019-12-29
      相关资源
      最近更新 更多