【问题标题】:Django : Order by position ignoring NULLDjango:按位置排序,忽略 NULL
【发布时间】:2011-07-11 05:59:34
【问题描述】:

我对 django 查询集排序有疑问。

我的模型包含一个名为 position 的字段(一个 PositiveSmallIntegerField),我想用它来对查询结果进行排序。

我使用order_by('position'),效果很好。

问题:我的 position 字段可以为空 (null=True, blank=True),因为我不想为我的模型的每 50000 个实例指定一个位置:(

当某些实例的“位置”为 NULL 时,order_by 会将它们返回到列表的顶部:我希望它们位于最后...

在 RAW SQL 中,我曾经写过诸如“IF(position IS NULL or position='', 1, 0)”之类的东西(参见http://www.shawnolson.net/a/730/mysql-sort-order-with-null.html):是否可以使用 Django 获得相同的结果,而无需编写原始 SQL?

非常感谢!

【问题讨论】:

    标签: django sql-order-by nullable


    【解决方案1】:

    很遗憾在 SO 上有很多这样的问题没有标记为重复。请参阅(例如)this answer 了解 Django 1.11 及更高版本的本机解决方案。这是一个简短的摘录:

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

    示例用法(从评论到该答案):

    from django.db.models import F 
    MyModel.objects.all().order_by(F('price').desc(nulls_last=True))
    

    感谢原始答案作者和评论者。

    【讨论】:

      【解决方案2】:

      从 Django 1.8 开始,您可以使用 Coalesce()NULL 转换为 0

      示例:

      import datetime    
      from django.db.models.functions import Coalesce, Value
      
      from app import models
      
      
      # Coalesce works by taking the first non-null value.  So we give it
      # a date far before any non-null values of last_active.  Then it will
      # naturally sort behind instances of Box with a non-null last_active value.
      
      the_past = datetime.datetime.now() - datetime.timedelta(days=10*365)
      boxes = models.Box.objects.all().annotate(
          new_last_active=Coalesce(
              'last_active', Value(the_past)
          )
      ).order_by('-new_last_active')
      

      【讨论】:

      • 感谢该链接——这是我在日期时间属性上使用 Coalesce 的方式:gist.github.com/yosemitebandit/ec3adc02d927375f13d0
      • @Matt 的代码应该添加到答案中 - 它使它的用处增加了一倍
      • 我在答案中添加了@Matts 示例,所以不要忘记更新我们俩 :-)
      • 有一个问题,它说 unicode is missing tzinfo。
      • 很遗憾 Coalesce 也不适用于空字符串
      【解决方案3】:

      正如 Ignacio 所说,使用 extra() 可以优化很多最终查询。在我的应用程序中,我使用 extra() 而不是 annotate() 在数据库处理中节省了 500 多毫秒(对于查询来说已经很多)

      这是你的情况:

      items = Item.objects.all().extra(
          'select': {
              'null_position': 'CASE WHEN {tablename}.position IS NULL THEN 0 ELSE 1 END'
           }
      ).order_by('-null_position', 'position')
      

      {tablename} 应该类似于 {Item's app}_item,遵循 django 的默认表名。

      【讨论】:

        【解决方案4】:

        我发现 Pablo 答案中的语法需要在我的 1.7.1 安装中更新为以下内容:

        items = Item.objects.all().extra(select={'null_position': 'CASE WHEN {name of Item's table}.position IS NULL THEN 0 ELSE 1 END'}).order_by('-null_position', 'position')
        

        【讨论】:

          【解决方案5】:

          您可以使用 django agrregation 中的 annotate() 来解决问题:

          items = Item.objects.all().annotate(null_position=Count('position')).order_by('-null_position', 'position')
          

          【讨论】:

          • 不知何故这对我不起作用,可能是因为我有一个排序字段:position__name(即 position.name)。太糟糕了。啊,等等!我将 null_position 重命名为 null ,似乎 django 不喜欢这样!现在工作,谢谢! :)
          • 值得评论说这是一个多么棒的答案
          • 性能不会很好,因为它变成了一个聚合查询。
          • Django 1.11 内置了适当的支持:stackoverflow.com/a/44731168/237091
          【解决方案6】:

          QuerySet.extra() 可用于将表达式注入查询并按它们排序。

          【讨论】:

          • 现在已弃用。
          • .extra() 未被弃用。但它可能在未来。
          猜你喜欢
          • 2023-04-08
          • 1970-01-01
          • 2012-09-13
          • 1970-01-01
          • 2016-01-10
          • 2011-08-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多