2011-03-08 14 views
37

Tengo un problema con el orden de conjunto de consultas de django.Django: Ordenar por posición ignorando NULL

Mi modelo contiene un campo llamado position (a PositiveSmallIntegerField), que me gustaría utilizar para solicitar los resultados de la consulta.

Yo uso order_by('position'), que funciona muy bien.

Problema: position mi campo es anulable (null=True, blank=True), porque no wa no para especificar una posición por cada 50000 casos de mi modelo :(

Cuando algunos casos tienen un valor NULL "posición ", order_by los devuelve en la parte superior de la lista: me gustaría que fueran al final ...

en RAW SQL, solía escribir cosas como 'IF(position IS NULL or position='', 1, 0)' (ver http://www.shawnolson.net/a/730/mysql-sort-order-with-null.html): ¿es posible para obtener el mismo resultado usando Django, sin escribir SQL sin formato?

¡Muchas gracias!

Respuesta

52

Puede utilizar el annotate() desde django agrregation para hacer el truco:

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

¡Sí! Funciona bien, muchas gracias mnik700! – user650108

+1

Este es un truco muy bueno, gracias. –

+0

De alguna manera esto no funciona para mí, tal vez porque tengo como campo de ordenamiento: position__name (que es position.name). Demasiado. ¡Ah, espera! Cambié el nombre de null_position a null y parece que a django no le gusta eso. Trabajando ahora, gracias! :) – gabn88

1

QuerySet.extra() se puede utilizar para inyectar expresiones en la consulta y ordenarlas.

+0

Se está en desuso por ahora. – blueyed

+0

'.extra()' no está en desuso. Pero puede ser en el futuro. –

9

Usando extra() como se dijo Ignacio optimiza mucho la consulta final. En mi aplicacion que he ahorrado más de 500 ms (que es mucho para una consulta) en el procesamiento de base de datos utilizando extra() en lugar de anotar()

aquí es cómo se vería en su caso:

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} debe ser algo así como {Aplicación del artículo} _item siguiendo el nombre de las tablas predeterminadas de django.

+0

Sí, esta versión es mucho más rápida, ¡gracias! –

+0

'extra()' está en desuso, consulte http://stackoverflow.com/a/35494930/15690 en su lugar. – blueyed

3

me encontré con que la sintaxis en la respuesta de Pablo era necesario actualizar a la siguiente en mi instalación 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') 
16

A partir de Django 1.8 se puede utilizar para convertir Coalesce()NULL a 0.

muestra:

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') 
+6

gracias por ese enlace - así es como utilicé 'Coalesce' en los atributos de fecha y hora: https://gist.github.com/yosemitebandit/ec3adc02d927375f13d0 – Matt

+1

@ El código de Matt debe agregarse a la respuesta; lo hace dos veces más útil – LostMyGlasses

+0

I agregué una muestra de @Matts a mi respuesta, así que no olvides actualizarnos a nosotros dos :-) – shredding