2011-10-13 17 views
33

Mam model zleceń pracy, z polem, w którym wymagane jest zlecenie pracy. Aby uzyskać listę zleceń, z tymi, które są wymagane na początku, mogę to zrobić:Django Sortuj według daty, ale na końcu "Brak"?

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

Działa to dobrze, ale tylko wtedy, gdy jest rzeczywiście wartość w tym polu. Jeśli nie ma wymaganej daty, wówczas wartością jest None. Następnie lista zleceń roboczych zawiera wszystkie numery None na górze, a następnie pozostałe zlecenia robocze w odpowiedniej kolejności.

Jak mogę uzyskać None na dole?

+0

Zobacz http://stackoverflow.com/a/35494930/ 15690, który używa specjalnej klasy Query/QuerySet. – blueyed

Odpowiedz

32
q = q.extra(select={ 
     'date_is_null': 'dateWORequired IS NULL', 
    }, 
    order_by=['date_is_null','dateWORequired'], 
) 

Możesz potrzebować - przed date_is_null w części order_by, ale w ten sposób możesz kontrolować zachowanie.

+0

Genialny! Działa jak marzenie. Teraz muszę znaleźć dokumentację tego kodu, aby zrozumieć, co się dzieje. Mimo to dziękuję. – Garfonzo

+4

Podczas gdy to działa, używa niestandardowego kodu SQL, którego najlepiej unikać, jeśli to możliwe.Alternatywa, która nie używa niestandardowego kodu SQL, jest podana w zaakceptowanej odpowiedzi na [Django: Zamówienie według pozycji ignorującej NULL] (https://stackoverflow.com/questions/5235209/django-order-by-position-ignoring-null) . – markshep

+1

'extra' jest przestarzałe i nie działa z obcymi kluczami/połączonymi tabelami. – blueyed

0

Starałem się, aby to działało z czystym Django, bez wpadania na SQL.

Funkcja wyrażenia F() może być używana z funkcją order_by, więc próbowałem wymyślić sposób tworzenia wyrażenia, który ustawia wszystkie liczby na tę samą wartość, ale który ustawia wszystkie wartości NULL na inną określoną wartość.

MySQL zamawia NULL przed 0 w kolejności rosnącej i odwrotnie w porządku malejącym.

Tak to działa:

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

Następnie można przejść do innych pól tego samego połączenia order_by, przed lub po tej wypowiedzi.

Próbowałem już z datami i to samo się dzieje. np:

SELECT 0*CURRENT_TIMESTAMP; 

Ocenia 0.

5

To nie był dostępny, gdy padło pytanie, ale ponieważ Django 1.8 Myślę, że to najlepsze rozwiązanie:

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 wybiera pierwszy wartość inna niż null, więc tworzysz wartość date_null, według której zamówienie ma wartość dateWORequired, ale z null zastąpiono datą dawno temu.

+0

Biorąc pod uwagę fakt, że '.extra()' może wkrótce zostać uznane za przestarzałe, powinno to być zaakceptowaną odpowiedzią. –

+1

Działa również z Django 1.10 - to po prostu 'from django.db.models import Value' i zaginąłeś i') 'po wartości' Value (long_ago) ' – Kroenig

4

Wymagania: Python 3.4, Django 10.2, PostgreSQL 9.5.4

Wariant 1

Rozwiązanie:

class IsNull(models.Func): 

    template = "%(expressions)s IS NULL" 

Usage (None zawsze najnowsza):

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 

Uwagi

  1. podstawie https://www.isotoma.com/blog/2015/11/23/sorting-querysets-with-nulls-in-django/
  2. wadę: pole niepotrzebny bufor podwieszone do zamawiania

Wariant 2

Rozwiązanie:

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) 

Zastosowanie:

# 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() 

wyników (brak zawsze najnowsza):

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 

Uwagi:

  1. podstawie Django: Adding "NULLS LAST" to query i kodu źródłowego Django`s

  2. Globalny na wszystkich polach typu (to jest zaletą i wadą jednocześnie)

  3. Nie a zbędny obszar

  4. Wadą - testowany na PostgreSQL

12

Django 1,11 dodanej jest to funkcja natywna. Jest trochę zawiłe i niezbyt dobrze udokumentowane.

Zamówiliśmy tylko jednym polu, rosnąco:

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

Zamówione użyciu dwóch pól, zarówno malejąco:

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

Wygląda bardzo obiecująco, ale nie mogę go uruchomić. Masz jakieś pomysły? Plik "/Users/erik/env/swida/lib/python3.5/site-packages/django/db/models/base.py", wiersz 1653, w pola = ((f [1:] if f.startswith ("-") else f) dla f w polach) AttributeError: Obiekt "OrderBy" nie ma atrybutu "startswith" –