Django Order By Date, но есть "None" в конце? - PullRequest
42 голосов
/ 13 октября 2011

У меня есть модель рабочих заданий с полем, когда требуется рабочий заказ. Чтобы получить список заказов на работу, с теми, которые требуются рано, я делаю это:

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

Это хорошо работает, но ТОЛЬКО если в этом поле есть значение. Если требуемой даты нет, то значение равно None. Затем список рабочих заданий имеет все None вверху, а затем остальные рабочие задания в правильном порядке.

Как я могу получить None внизу?

Ответы [ 5 ]

39 голосов
/ 24 июня 2017

Django 1.11 добавил это как нативную функцию.Это немного запутанно. Это задокументировано .

Упорядочено только с одним полем, по возрастанию :

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))
37 голосов
/ 13 октября 2011
q = q.extra(select={
        'date_is_null': 'dateWORequired IS NULL',
    },
    order_by=['date_is_null','dateWORequired'],
)

Вам может потребоваться - перед date_is_null в части order_by, но именно так вы можете контролировать поведение.

8 голосов
/ 24 марта 2016

Это было недоступно, когда был задан вопрос, но, начиная с 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 для заказа, по которому просто требуется dateWORequire, но с null, замененным датой давно.

4 голосов
/ 31 октября 2016

Требования: 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

Примечания

  1. На основе https://www.isotoma.com/blog/2015/11/23/sorting-querysets-with-nulls-in-django/
  2. Недостаток: ненужное поле буфера, накладные расходы на заказ

Вариант 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

Примечания:

  1. На основе Django: добавление «NULLS LAST» для запроса и исходного кода Django

  2. Глобальный для всех полей модели (это преимущество и недостаток одновременно)

  3. Нет ненужного поля

  4. Недостаток - проверено только на PostgreSQL

1 голос
/ 29 января 2016

Я попытался заставить это работать с чистым Django, не вдаваясь в SQL.

Функцию выражения F () можно использовать с order_by, поэтому я попытался придумать способ создания выражения, которое присваивает всем числам одно и то же значение, но которое присваивает всем NULL другое конкретное значение.

MySQL упорядочит NULL до 0 в порядке возрастания и наоборот в порядке убывания.

Так что это работает:

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

Затем вы можете передать любые другие поля тому же вызову order_by, до или после этого выражения.

Я пробовал это с датами, и то же самое происходит. e.g.:

SELECT 0*CURRENT_TIMESTAMP;

Оценивает до 0.

...