100 votes

Django : Ajout de "NULLS LAST" à une requête

Je voudrais trier un modèle en utilisant l'option "NULLS LAST" de Postgresql. Comment cela peut-il être fait ?

J'ai essayé quelque chose comme

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

Mais j'ai

"Impossible de résoudre le mot-clé 'NULLS LAST' dans le champ".

3voto

Edward D'Souza Points 185

La réponse de @kabucey est la meilleure solution pour Django >= 1.11, mais si vous utilisez au moins Django 1.8, 1.9 ou 1.10, vous pouvez utiliser un fichier Func pour obtenir le comportement "NULLS Last", comme décrit à l'adresse suivante https://www.isotoma.com/blog/2015/11/23/sorting-querysets-with-nulls-in-django/ :

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',
        )

Le premier order_by trie la liste dans l'ordre croissant par price_isnull en forçant les articles à prix nul à la fin de la liste puisque True > False .

2voto

stdword Points 21

Il existe un autre moyen d'ajouter la fonctionnalité de nullités gérées à Django < v1.11 avec le style Django v1.11 :

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

Cons :

  1. Migration facile vers Django 1.11
  2. Nous n'entrons pas dans les détails du compilateur de requêtes.

Pour ce faire, nous devons surcharger les classes django.db.models.F et 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)

1voto

Kevin Points 4235

Nous voulions enchaîner plusieurs instructions d'ordre par, certaines ASC, d'autres DESC, toutes avec NULLS LAST. Il ne semble pas que cela soit possible avec order_by car il a l'appel suivant :

obj.query.clear_ordering(force_empty=False)

Vous pouvez donc le faire avec ce qui suit en ajoutant des appels 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...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X