Django 按日期排序,但最后有“无”?

Posted

技术标签:

【中文标题】Django 按日期排序,但最后有“无”?【英文标题】:Django Order By Date, but have "None" at end? 【发布时间】:2011-12-06 15:30:09 【问题描述】:

我有一个工作订单模型,其中包含一个工作订单何时需要的字段。为了获得一份工单清单,以及那些早期需要的工单,我这样做:

wo = Work_Order.objects.order_by('dateWORequired')

这很好用,但前提是该字段中确实有一个值。如果没有要求的日期,则值为None。然后,工单列表的顶部是所有None,然后是其余工单以正确的顺序排列。

我怎样才能得到底部的None

【问题讨论】:

参见***.com/a/35494930/15690,它使用了一个特殊的 Query / QuerySet 类。 【参考方案1】:

Django 1.11 将此作为原生功能添加。这有点令人费解。 It is documented.

只有一个字段,升序

wo = Work_Order.objects.order_by(F('dateWORequired').asc(nulls_last=True))

使用两个字段排序,均降序

wo = Work_Order.objects.order_by(F('dateWORequired').desc(nulls_last=True), F('anotherfield').desc(nulls_last=True))

【讨论】:

看起来很有希望,但我无法让它发挥作用。请问有什么想法吗?文件“/Users/erik/env/swida/lib/python3.5/site-packages/django/db/models/base.py”,第 1653 行,在 字段 = ((f[1:] if f .startswith('-') else f) for f in fields) AttributeError: 'OrderBy' object has no attribute 'startswith'【参考方案2】:
q = q.extra(select=
        'date_is_null': 'dateWORequired IS NULL',
    ,
    order_by=['date_is_null','dateWORequired'],
)

您可能需要在 order_by 部分的 date_is_null 之前添加 -,但这就是您可以控制行为的方式。

【讨论】:

太棒了!奇迹般有效。现在我需要查找该代码的文档以了解发生了什么。不过还是谢谢。 虽然可行,但它确实使用自定义 SQL,如果可能,最好避免使用。在接受Django : Order by position ignoring NULL 的答案中给出了不使用自定义 SQL 的替代方法。 extra 已弃用,不适用于外键/连接表。 @blueyed 不,extra 没有被弃用,尽管它计划使用deprecate at some point in the future【参考方案3】:

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

from django.db.models import Coalesce, Value
long_ago = datetime.datetime(year=1980, month=1, day=1)
Work_Order.objects.order_by('dateWORequired')
MyModel.objects.annotate(date_null=
    Coalesce('dateWORequired', Value(long_ago))).order_by('date_null')

Coalesce 选择第一个非空值,因此您创建一个值 date_null 来排序,它只是 dateWORequired 但 null 替换为很久以前的日期。

【讨论】:

鉴于.extra()可能很快会被弃用,这应该是公认的答案。 也适用于 Django 1.10 - 它只是 from django.db.models import Value 而你在 Value(long_ago) 之后丢失了 )【参考方案4】:

要求: Python 3.4、Django 10.2、PostgreSQL 9.5.4

变体 1

解决方案:

class IsNull(models.Func):

    template = "%(expressions)s IS NULL"

使用(无总是最新的):

In [1]: a = User.polls_manager.users_as_voters()

In [4]: from django.db import models

In [5]: class IsNull(models.Func):
   ...:     template = "%(expressions)s IS NULL"
   ...:     

In [7]: a = a.annotate(date_latest_voting_isnull=IsNull('date_latest_voting'))

In [9]: for i in a.order_by('date_latest_voting_isnull', 'date_latest_voting'):
   ...:     print(i.date_latest_voting)
   ...:     
2016-07-30 01:48:11.872911+00:00
2016-08-31 13:13:47.240085+00:00
2016-09-16 00:04:23.042142+00:00
2016-09-18 19:45:54.958573+00:00
2016-09-26 07:27:34.301295+00:00
2016-10-03 14:01:08.377417+00:00
2016-10-21 16:07:42.881526+00:00
2016-10-23 11:10:02.342791+00:00
2016-10-31 04:09:03.726765+00:00
None

In [10]: for i in a.order_by('date_latest_voting_isnull', '-date_latest_voting'):
    ...:     print(i.date_latest_voting)
    ...:     
2016-10-31 04:09:03.726765+00:00
2016-10-23 11:10:02.342791+00:00
2016-10-21 16:07:42.881526+00:00
2016-10-03 14:01:08.377417+00:00
2016-09-26 07:27:34.301295+00:00
2016-09-18 19:45:54.958573+00:00
2016-09-16 00:04:23.042142+00:00
2016-08-31 13:13:47.240085+00:00
2016-07-30 01:48:11.872911+00:00
None

注意事项

    基于https://www.isotoma.com/blog/2015/11/23/sorting-querysets-with-nulls-in-django/ 缺点:不必要的缓冲区字段,排序开销

变体 2

解决方案:

from django.db import models
from django.db import connections
from django.db.models.sql.compiler import SQLCompiler


class NullsLastCompiler(SQLCompiler):

    # source code https://github.com/django/django/blob/master/django/db/models/sql/compiler.py

    def get_order_by(self):

        result = super(NullsLastCompiler, self).get_order_by()

        # if result exists and backend is PostgreSQl
        if result and self.connection.vendor == 'postgresql':

            # modified raw SQL code to ending on NULLS LAST after ORDER BY
            # more info https://www.postgresql.org/docs/9.5/static/queries-order.html
            result = [
                (expression, (sql + ' NULLS LAST', params, is_ref))
                for expression, (sql, params, is_ref) in result
            ]

        return result


class NullsLastQuery(models.sql.Query):

    # source code https://github.com/django/django/blob/master/django/db/models/sql/query.py
    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 own compiler
        return NullsLastCompiler(self, connection, using)


class NullsLastQuerySet(models.QuerySet):

    # source code https://github.com/django/django/blob/master/django/db/models/query.py
    def __init__(self, model=None, query=None, using=None, hints=None):

        super(NullsLastQuerySet, self).__init__(model, query, using, hints)

        # replace on own Query
        self.query = query or NullsLastQuery(model)

用法:

# instead of models.QuerySet use NullsLastQuerySet
class UserQuestionQuerySet(NullsLastQuerySet):

    def users_with_date_latest_question(self):

        return self.annotate(date_latest_question=models.Max('questions__created'))


#connect to a model as a manager
class User(AbstractBaseUser, PermissionsMixin):
    .....

    questions_manager = UserQuestionQuerySet().as_manager()

结果(无总是最新的):

In [2]: qs = User.questions_manager.users_with_date_latest_question()

In [3]: for i in qs:
   ...:     print(i.date_latest_question)
   ...:     
None
None
None
2016-10-28 20:48:49.005593+00:00
2016-10-04 19:01:38.820993+00:00
2016-09-26 00:35:07.839646+00:00
None
2016-07-27 04:33:58.508083+00:00
2016-09-14 10:40:44.660677+00:00
None

In [4]: for i in qs.order_by('date_latest_question'):
   ...:     print(i.date_latest_question)
   ...:     
2016-07-27 04:33:58.508083+00:00
2016-09-14 10:40:44.660677+00:00
2016-09-26 00:35:07.839646+00:00
2016-10-04 19:01:38.820993+00:00
2016-10-28 20:48:49.005593+00:00
None
None
None
None
None

In [5]: for i in qs.order_by('-date_latest_question'):
   ...:     print(i.date_latest_question)
   ...:     
2016-10-28 20:48:49.005593+00:00
2016-10-04 19:01:38.820993+00:00
2016-09-26 00:35:07.839646+00:00
2016-09-14 10:40:44.660677+00:00
2016-07-27 04:33:58.508083+00:00
None
None
None
None
None

注意事项:

    基于Django: Adding "NULLS LAST" to query和Django的源代码

    在一个模型的所有领域都是全局的(同时是优势和劣势)

    没有不必要的字段

    一个缺点 - 仅在 PostgreSQL 上测试

【讨论】:

【参考方案5】:

我努力在不使用 SQL 的情况下使用纯 Django 来实现这一点。

F() 表达式函数可以与 order_by 一起使用,因此我尝试了一种创建表达式的方法,它将所有数字设置为相同的值,但将所有 NULL 设置为另一个特定值。

mysql 会在 0 之前按升序排列 NULL,反之则按降序排列。

所以这行得通:

order_by( (0 * F('field')).asc() ) # Nulls first
# or:
order_by( (0 * F('field')).desc() ) # Nulls last

然后,您可以在该表达式之前或之后将任何其他字段传递给同一个 order_by 调用。

我已经用日期试过了,同样的情况发生了。例如:

SELECT 0*CURRENT_TIMESTAMP;

评估为 0。

【讨论】:

以上是关于Django 按日期排序,但最后有“无”?的主要内容,如果未能解决你的问题,请参考以下文章

django-rest-framework 按日期过滤=无

django 查询集按小时、分钟和秒排序

获取按日期排序的最后记录的最佳性能方法

在PHP中按日期排序数组

获取 Spring Data 上按日期排序的最后记录

按日期时间的月/日订购 Django QuerySet?