Django obtiene un QuerySet de una matriz de identificadores en un orden específico


83

aquí hay uno rápido para ti:

Tengo una lista de identificaciones que quiero usar para devolver un QuerySet (o una matriz si es necesario), pero quiero mantener ese orden.

Gracias

Respuestas:


73

No creo que pueda hacer cumplir ese orden en particular en el nivel de la base de datos, por lo que debe hacerlo en Python.

id_list = [1, 5, 7]
objects = Foo.objects.filter(id__in=id_list)

objects = dict([(obj.id, obj) for obj in objects])
sorted_objects = [objects[id] for id in id_list]

Esto crea un diccionario de los objetos con su ID como clave, por lo que se pueden recuperar fácilmente al crear la lista ordenada.


Esperaba que este no fuera el caso :( ¡gracias por el código limpio!
neolaser

3
Solo tenga cuidado con las consultas grandes, ya que almacenará el resultado en la memoria al hacer esto.
Jj.

13
¿Quizás usar el método querysets in_bulk (), en lugar de construir el diccionario usted mismo?
Matt Austin

El problema con esto es que si un objeto en id_list no existe, arrojará un error.
madprops

¿Por qué no utilizar la comprensión de dict?
wieczorek1990

180

Desde Django 1.8, puede hacer:

from django.db.models import Case, When

pk_list = [10, 2, 1]
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(pk_list)])
queryset = MyModel.objects.filter(pk__in=pk_list).order_by(preserved)

15
La mejor respuesta porque en realidad devuelve un conjunto de consultas
Teebes

1
¡Sigo pensando que los django Case Whenestán subestimados!
Babu

Voy a usar distinct()con order_by case when cláusula pero obtuve el error. cualquier apoyo, por favor.
elquimista

SELECT DISTINCT ON expressions must match initial ORDER BY expressions- aquí está el mensaje de error
elquimista

¡Recomiendo encarecidamente que esta es la mejor respuesta!
Tony

28

Si desea hacer esto usando in_bulk, en realidad debe combinar las dos respuestas anteriores:

id_list = [1, 5, 7]
objects = Foo.objects.in_bulk(id_list)
sorted_objects = [objects[id] for id in id_list]

De lo contrario, el resultado será un diccionario en lugar de una lista ordenada específicamente.


1
Esta no es una mejor respuesta.
Tony

25

He aquí una forma de hacerlo a nivel de base de datos. Copiar y pegar de: blog.mathieu-leplatre.info :

MySQL :

SELECT *
FROM theme
ORDER BY FIELD(`id`, 10, 2, 1);

Lo mismo con Django:

pk_list = [10, 2, 1]
ordering = 'FIELD(`id`, %s)' % ','.join(str(id) for id in pk_list)
queryset = Theme.objects.filter(pk__in=[pk_list]).extra(
           select={'ordering': ordering}, order_by=('ordering',))

PostgreSQL :

SELECT *
FROM theme
ORDER BY
  CASE
    WHEN id=10 THEN 0
    WHEN id=2 THEN 1
    WHEN id=1 THEN 2
  END;

Lo mismo con Django:

pk_list = [10, 2, 1]
clauses = ' '.join(['WHEN id=%s THEN %s' % (pk, i) for i, pk in enumerate(pk_list)])
ordering = 'CASE %s END' % clauses
queryset = Theme.objects.filter(pk__in=pk_list).extra(
           select={'ordering': ordering}, order_by=('ordering',))

Guau. Eso es intenso. ¡Gracias!
Dan Gayle

Gracias por esta respuesta.
Subangkar KrS

9
id_list = [1, 5, 7]
objects = Foo.objects.filter(id__in=id_list)
sorted(objects, key=lambda i: id_list.index(i.pk))

1
Agregue un texto que explique cómo funciona esto y por qué resolvería el problema del OP. Ayude a otros a comprender.
APC

La mejor respuesta. ¡Gracias!
webjunkie

Funciona bien, aunque podría usar información adicional
xtrinch

esto funcionará solo para la lista de identificación ordenada. ¿Puedes confirmar si funcionará en alguna secuencia ... no me parece cierto?
Doogle
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.