41 votes

Django : Ordre par position ignorant les NULL

J'ai un problème avec l'ordre des querysets de django.

Mon modèle contient un champ nommé position (a Champ de petits nombres positifs ), que j'aimerais utiliser pour ordonner les résultats des requêtes.

J'utilise order_by('position') qui fonctionne très bien.

Problème : mon position est nullable ( null=True, blank=True ), car je ne veux pas spécifier une position pour toutes les 50000 instances de mon modèle :(

Lorsque certaines instances ont une "position" NULL, order_by les renvoie en haut de la liste : j'aimerais qu'ils soient à la fin...

Dans RAW SQL, j'avais l'habitude d'écrire des choses comme " IF(position IS NULL or position='', 1, 0) "(voir http://www.shawnolson.net/a/730/mysql-sort-order-with-null.html ) : est-il possible d'obtenir le même résultat en utilisant Django, sans écrire du SQL brut ?

Merci beaucoup !

57voto

umnik700 Points 6243

Vous pouvez utiliser la fonction annotate() de la fonction agrégation django pour faire l'affaire :

items = Item.objects.all().annotate(null_position=Count('position')).order_by('-null_position', 'position')

0 votes

D'une manière ou d'une autre, cela ne fonctionne pas pour moi, peut-être parce que j'ai comme champ de commande : position__name (qui est position.name). C'est dommage. Ah, attendez ! J'ai renommé le null_position en null et il semble que django n'aime pas ça ! Cela fonctionne maintenant, merci ! :)

0 votes

Un commentaire pour dire que c'est une excellente réponse.

0 votes

Les performances ne seront pas bonnes puisqu'il s'agit d'une requête agrégée.

20voto

shredding Points 736

À partir de Django 1.8, vous pouvez utiliser Coalesce() pour convertir NULL a 0 .

Échantillon :

import datetime    
from django.db.models.functions import Coalesce, Value

from app import models

# Coalesce works by taking the first non-null value.  So we give it
# a date far before any non-null values of last_active.  Then it will
# naturally sort behind instances of Box with a non-null last_active value.

the_past = datetime.datetime.now() - datetime.timedelta(days=10*365)
boxes = models.Box.objects.all().annotate(
    new_last_active=Coalesce(
        'last_active', Value(the_past)
    )
).order_by('-new_last_active')

12voto

dvk Points 1160

C'est dommage qu'il y ait beaucoup de questions comme celle-ci sur SO qui ne sont pas marquées comme dupliquées. Voir (par exemple) cette réponse pour la solution native pour Django 1.11 et plus récent. En voici un court extrait :

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

Exemple d'utilisation (du commentaire à cette réponse) :

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

Le crédit revient à l'auteur de la réponse originale et au commentateur.

9voto

Pablo Abdelhay Points 178

L'utilisation de extra() comme l'a dit Ignacio optimise beaucoup la requête finale. Dans mon application, j'ai économisé plus de 500 ms (c'est beaucoup pour une requête) dans le traitement de la base de données en utilisant extra() au lieu de annotate().

Voici à quoi cela ressemblerait dans votre cas :

items = Item.objects.all().extra(
    'select': {
        'null_position': 'CASE WHEN {tablename}.position IS NULL THEN 0 ELSE 1 END'
     }
).order_by('-null_position', 'position')

{tablename} devrait être quelque chose comme L'application de {Item}_item en suivant le nom des tables par défaut de django.

3voto

sbemagx Points 41

J'ai trouvé que la syntaxe dans la réponse de Pablo devait être mise à jour comme suit sur mon installation 1.7.1 :

items = Item.objects.all().extra(select={'null_position': 'CASE WHEN {name of Item's table}.position IS NULL THEN 0 ELSE 1 END'}).order_by('-null_position', 'position')

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