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".

172voto

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

Cette fonctionnalité a été ajoutée à Django 1.11.

https://docs.djangoproject.com/en/dev/releases/1.11/

Ajouté les paramètres nulls_first et nulls_last à Expression.asc() et desc() pour contrôler l'ordre des valeurs nulles.

Référence pour Django 3.1 : https://docs.djangoproject.com/en/3.1/ref/models/expressions/#using-f-to-sort-null-values

23voto

Mariano Points 783

La chose la plus proche que j'ai trouvée est de le faire sur deux marches. On commande d'abord sur le champ rempli, puis sur les champs nuls :

Via ce gist (lui-même via ces journaux de django ):

all_projects = Project.objects.select_related().filter(
    company=company).order_by('-date_due')

q = all_projects.extra(select={'date_due_null': 'date_due is null'})
q = q.extra(order_by=['date_due_null'])
print q.query

Attention : notez le avertissements concernant extra() et qu'il pourrait être déprécié à l'avenir. .

22voto

Tim Babych Points 399

Si vous voulez que cela soit fait de manière transparente et sur toutes les colonnes, vous pouvez redéfinir la génération sql. Pour ce faire, vous devriez avoir votre propre gestionnaire pour retourner votre QuerySet personnalisé pour retourner votre Query personnalisé pour utiliser le compilateur personnalisé. Mon code pour cela ressemble à cela (Django 1.5) :

from django.db import models, connections

class NullsLastQuery(models.sql.query.Query):
    """
    Query that uses custom compiler,
    to utilize PostgreSQL feature of setting position of NULL records
    """
    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]

        # defining that class elsewhere results in import errors
        from django.db.models.sql.compiler import SQLCompiler
        class NullsLastSQLCompiler(SQLCompiler):
            def get_ordering(self):
                result, group_by = super(NullsLastSQLCompiler, self
                    ).get_ordering()
                if self.connection.vendor == 'postgresql' and result:
                    result = [line + " NULLS LAST" for line in result]
                return result, group_by

        return NullsLastSQLCompiler(self, connection, using)

class NullsLastQuerySet(models.query.QuerySet):
    def __init__(self, model=None, query=None, using=None):
        super(NullsLastQuerySet, self).__init__(model, query, using)
        self.query = query or NullsLastQuery(self.model)

class NullsLastManager(models.Manager):
    def get_query_set(self):
        return NullsLastQuerySet(self.model, using=self._db)

class YourModel(models.Model):
    objects = NullsLastManager()

13voto

Mark Points 1278

Cette option n'était probablement pas disponible lorsque la question a été posée, mais depuis Django 1.8, je pense que c'est la meilleure solution :

from django.db.models import Coalesce, Value
MyModel.objects.all().annotate(price_null=
    Coalesce('price', Value(-100000000)).order_by('-price_null')

Coalesce sélectionne la première valeur non nulle, vous créez donc une valeur price_null pour commander par qui est juste le prix mais avec null remplacé par -100000000 (o + ?).

11voto

blueyed Points 7719

Pour Django 1.9 (et éventuellement 1.8), vous pouvez utiliser ceci :

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

class NullsLastSQLCompiler(SQLCompiler):
    def get_order_by(self):
        result = super().get_order_by()
        if result and self.connection.vendor == 'postgresql':
            return [(expr, (sql + ' NULLS LAST', params, is_ref))
                    for (expr, (sql, params, is_ref)) in result]
        return result

class NullsLastQuery(models.sql.query.Query):
    """Use a custom compiler to inject 'NULLS LAST' (for PostgreSQL)."""

    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 NullsLastSQLCompiler(self, connection, using)

class NullsLastQuerySet(models.QuerySet):
    def __init__(self, model=None, query=None, using=None, hints=None):
        super().__init__(model, query, using, hints)
        self.query = query or NullsLastQuery(self.model)

Et ensuite sur votre (vos) modèle(s) :

objects = NullsLastQuerySet.as_manager()

Ceci est basé sur la réponse de Tim dans https://stackoverflow.com/a/17077587/15690 .

Le ticket pour ajouter ce support à Django a été réouvert : https://code.djangoproject.com/ticket/13312 .

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