【问题标题】:Django: Adding "NULLS LAST" to queryDjango:在查询中添加“NULLS LAST”
【发布时间】:2013-02-13 18:59:42
【问题描述】:

我想使用 Postgresql 的“NULLS LAST”选项对模型进行排序。 怎么可能?

我尝试了类似的东西

MyModel.objects.all().extra(order_by=('-price', 'NULLS LAST'))

但我明白了

“无法将关键字 'NULLS LAST' 解析为字段”

【问题讨论】:

    标签: python sql django


    【解决方案1】:
    from django.db.models import F  
    MyModel.objects.all().order_by(F('price').desc(nulls_last=True))
    

    此功能已添加到 Django 1.11。

    https://docs.djangoproject.com/en/dev/releases/1.11/

    将 nulls_first 和 nulls_last 参数添加到 Expression.asc() 和 desc() 来控制 null 值的顺序。

    Django 3.1 参考:https://docs.djangoproject.com/en/3.1/ref/models/expressions/#using-f-to-sort-null-values

    【讨论】:

    • 有没有办法将此添加到模型的Meta 类中?或者我们是否也在做同样的事情,在Meta 的ordering 字段中使用F 对象?
    • 结果查询是否有任何额外开销?我想在将传递排序字段的通用方法中使用它。有些字段不是空字段,因此它们不是必需的。有些字段也是空的。对于非空字段,是否会增加查询的额外开销?
    • 这在您想要对元素进行排序并使用排序查询时非常有用,例如在“为”。我发现在 first() / last() 中使用描述的顺序有问题。它们都返回查询的第一个元素。
    • 我写错了MyModel.objects.filter(something=something).order_by(F('fieldname')).desc(nulls_last=True)。它正确地写为MyModel.objects.filter(something=something).order_by(F('fieldname').desc(nulls_last=True))。请注意,.desc() 或 .asc() 正在修改 F()。如果您考虑一下,这是完全有道理的,但我没有考虑过。
    【解决方案2】:

    如果您希望它在所有列上透明地完成,您可以重新定义 sql 生成。为此,您需要拥有自己的 Manager 来返回您的自定义 QuerySet 以返回您的自定义 Query 以使用自定义编译器。我的代码看起来像这样(Django 1.5):

    from django.db import models, connections
    
    class NullsLastQuery(models.sql.query.Query):
        """
        Query that uses custom compiler,
        to utilize PostgreSQL feature of setting position of NULL records
        """
        def get_compiler(self, using=None, connection=None):
            if using is None and connection is None:
                raise ValueError("Need either using or connection")
            if using:
                connection = connections[using]
    
            # defining that class elsewhere results in import errors
            from django.db.models.sql.compiler import SQLCompiler
            class NullsLastSQLCompiler(SQLCompiler):
                def get_ordering(self):
                    result, group_by = super(NullsLastSQLCompiler, self
                        ).get_ordering()
                    if self.connection.vendor == 'postgresql' and result:
                        result = [line + " NULLS LAST" for line in result]
                    return result, group_by
    
            return NullsLastSQLCompiler(self, connection, using)
    
    class NullsLastQuerySet(models.query.QuerySet):
        def __init__(self, model=None, query=None, using=None):
            super(NullsLastQuerySet, self).__init__(model, query, using)
            self.query = query or NullsLastQuery(self.model)
    
    class NullsLastManager(models.Manager):
        def get_query_set(self):
            return NullsLastQuerySet(self.model, using=self._db)
    
    class YourModel(models.Model):
        objects = NullsLastManager()
    

    【讨论】:

    • 这有点邪恶,但很精彩。
    【解决方案3】:

    我发现最接近的事情是分两步完成。首先对填充字段排序,然后对空值排序:

    通过this gist(本身通过这些django logs):

    all_projects = Project.objects.select_related().filter(
        company=company).order_by('-date_due')
    
    q = all_projects.extra(select={'date_due_null': 'date_due is null'})
    q = q.extra(order_by=['date_due_null'])
    print q.query
    

    注意:注意warnings regarding extra(), and that it may be deprecated in the future。

    【讨论】:

    • 不客气。我不认为有一个完美的解决方案,或者至少是 Django 直接考虑的,由 Malcolm Treddinick 给出的this google groups post。
    【解决方案4】:

    当问题被问到时,这可能不可用,但从 Django 1.8 开始,我认为这是最好的解决方案:

    from django.db.models import Coalesce, Value
    MyModel.objects.all().annotate(price_null=
        Coalesce('price', Value(-100000000)).order_by('-price_null')
    

    Coalesce 选择第一个非空值,因此您创建一个值price_null 来订购,这只是价格,但null 替换为-100000000(或+?)。

    【讨论】:

    • 这实际上是一个很棒的 hack,因为它不需要创建 NULLS LAST 索引之类的东西来加速查询
    • @valentjedi 这不需要Coalesce('price', Value(-100000000) 的表达式索引吗?这与创建 NULLS LAST 索引相同。这里dba.stackexchange.com/a/99009有人说和我说的一样
    • @jperelli 实际上你是对的。但在我的情况下,这个 hack 比添加 NULLS LAST 更快,所以我很满意。
    • 这对我有用。我将它与 django 1.11 的子查询表达式一起使用
    • 值在 django.db.models.functions 模块中不可用。它在 django.db.models 模块中可用。
    【解决方案5】:

    对于 Django 1.9(可能还有 1.8),您可以使用这个:

    from django.db import connections, models
    from django.db.models.sql.compiler import SQLCompiler
    
    
    class NullsLastSQLCompiler(SQLCompiler):
        def get_order_by(self):
            result = super().get_order_by()
            if result and self.connection.vendor == 'postgresql':
                return [(expr, (sql + ' NULLS LAST', params, is_ref))
                        for (expr, (sql, params, is_ref)) in result]
            return result
    
    
    class NullsLastQuery(models.sql.query.Query):
        """Use a custom compiler to inject 'NULLS LAST' (for PostgreSQL)."""
    
        def get_compiler(self, using=None, connection=None):
            if using is None and connection is None:
                raise ValueError("Need either using or connection")
            if using:
                connection = connections[using]
            return NullsLastSQLCompiler(self, connection, using)
    
    
    class NullsLastQuerySet(models.QuerySet):
        def __init__(self, model=None, query=None, using=None, hints=None):
            super().__init__(model, query, using, hints)
            self.query = query or NullsLastQuery(self.model)
    

    然后在您的模型上:

    objects = NullsLastQuerySet.as_manager()
    

    这是基于 Tim 在https://stackoverflow.com/a/17077587/15690 中的回答。

    向 Django 添加对此支持的票已重新打开:https://code.djangoproject.com/ticket/13312。

    【讨论】:

    • 您对如何在末尾添加空字符串的功能有任何想法吗?
    • @Duncan 目前没有得到报酬,抱歉.. ;)
    • @blueyed,没问题。通过使用带有 Case 和 When 表达式组合的注释,我能够让它很好地工作
    【解决方案6】:

    @kabucey's answer 最适合 Django >= 1.11,但如果您至少使用 Django 1.8、1.9 或 1.10,则可以使用自定义 Func 表达式来实现“NULLS Last”行为,如 @ 中所述987654322@:

    from django.db.models import Func
    
    class IsNull(Func):
        template = '%(expressions)s IS NULL'
    
    MyModel.objects.all().annotate(
        price_isnull=IsNull('price_isnull'),
        ).order_by(
            'price_isnull',
            '-price',
            )
    

    第一个order_by 参数按price_isnull 的升序对列表进行排序,将自True > False 以来的空价格项目强制到列表末尾。

    【讨论】:

      【解决方案7】:

      还有另一种方法可以使用 Django v1.11 样式向 Django

      from my_project.utils.django import F
      MyModel.objects.all().order_by(F('price').desc(nulls_last=True))
      # or
      MyModel.objects.all().order_by(F('price').desc().nullslast())
      

      缺点:

      1. 轻松迁移到 Django 1.11
      2. 我们不会深入到查询编译器内部

      为此,我们需要覆盖 django.db.models.F 和 django.db.models.expressions.OrderBy 类:

      from django.db.models import F as DjangoF
      from django.db.models.expression import OrderBy as DjangoOrderBy
      
      
      class OrderBy(DjangoOrderBy):
          def __init__(self, expression, descending=False, nulls_last=None):
              super(OrderBy, self).__init__(expression, descending)
              self.nulls_last = nulls_last
          ...
      
          def as_sql(self, compiler, connection, template=None, **extra_context):
              ...
              ordering_value = 'DESC' if self.descending else 'ASC'
              if self.nulls_last is not None:
                  nulls_value = 'LAST' if self.nulls_last else 'FIRST'
                  ordering_value += ' NULLS ' + nulls_value
      
              placeholders = {
                  'expression': expression_sql,
                  'ordering': ordering_value,
              }
              ...
      
          def nullslast(self):
              self.nulls_last = True
      
          def nullsfirst(self):
              self.nulls_last = False
      
      
      class F(DjangoF):
          ...
      
          def asc(self, nulls_last=None):
              return OrderBy(self, nulls_last=nulls_last)
      
          def desc(self, nulls_last=None):
              return OrderBy(self, descending=True, nulls_last=nulls_last)
      

      【讨论】:

        【解决方案8】:

        我们想用 NULLS LAST 链接多个 order by 语句,一些 ASC,一些 DESC。 order_by 似乎没有这种可能性,因为它有以下调用:

        obj.query.clear_ordering(force_empty=False)
        

        因此,您可以通过附加 add_ordering 调用来执行以下操作:

        qs = ATeamModel.objects.filter(whatever=1)
        qs.query.add_ordering(F('date_updated').desc(nulls_last=True))
        qs.query.add_ordering(F('date_created').desc(nulls_last=True))
        
        qs...
        

        【讨论】:

          猜你喜欢
          • 2020-02-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-02
          • 2022-01-07
          • 2011-04-10
          • 2013-04-24
          • 1970-01-01
          相关资源
          最近更新 更多