Consulta Django que obtiene los objetos más recientes de diferentes categorías

Tengo dos modelos A y B Todos B objetos B tienen una clave externa a un objeto A Dado un conjunto de objetos A , ¿existe alguna forma de usar el ORM para obtener un conjunto de objetos B que contenga el objeto más reciente creado para cada objeto A

Aquí hay un ejemplo simplificado:

 Class Bakery(models.Model): town = models.CharField() Class Cake(models.Model): bakery = models.ForeignKey(Bakery) baked_at = models.DateTimeField() 

Así que estoy buscando una consulta que devuelva el pastel más reciente horneado en cada panadería en Anytown, EE. UU.

Que yo sepa, no hay una manera de hacerlo en un solo paso en Django ORM.

Pero puedes dividirlo en dos consultas:

 bakeries = Bakery.objects.annotate( hottest_cake_baked_at=Max('cake__baked_at') ) hottest_cakes = Cake.objects.filter( baked_at__in=[b.hottest_cake_baked_at for b in bakeries] ) 

Si la identificación de los pasteles avanza junto con las marcas de tiempo de bake_at, puede simplificar y desambiguar el código anterior (en caso de que lleguen dos pasteles al mismo tiempo, puede obtener ambos):

 hottest_cake_ids = Bakery.objects.annotate( hottest_cake_id=Max('cake__id') ).values_list('hottest_cak‌​e_id', flat=True) hottest_cakes = Cake.objects.filter(id__in=hottest_cake_ids) 

Los créditos BTW para esto van para Daniel Roseman, quien una vez respondió una pregunta similar a la mía:

http://groups.google.pl/group/django-users/browse_thread/thread/3b3cd4cbad478d34/3e4c87f336696054?hl=pl&q=

Si el método anterior es demasiado lento, entonces también conozco un segundo método: puede escribir SQL personalizado produciendo solo aquellos Cakes, que son los más populares en las panaderías relevantes, definirlo como VIEW de la base de datos, y luego escribir el modelo Django no administrado para él. También se menciona en el hilo de usuarios de django anterior. El enlace directo al concepto original está aquí:

http://web.archive.org/web/20130203180037/http://wolfram.kriesing.de/blog/index.php/2007/django-nice-and-critical-article#comment-48425

Espero que esto ayude.

A partir de Django 1.11 y gracias a Subquery y OuterRef , finalmente podemos crear una consulta latest-per-group utilizando el ORM .

 hottest_cakes = Cake.objects.filter( baked_at=Subquery( (Cake.objects .filter(bakery=OuterRef('bakery')) .values('bakery') .annotate(last_bake=Max('baked_at')) .values('last_bake')[:1] ) ) ) #BONUS, we can now use this for prefetch_related() bakeries = Bakery.objects.all().prefetch_related( Prefetch('cake_set', queryset=hottest_cakes, to_attr='hottest_cakes' ) ) #usage for bakery in bakeries: print 'Bakery %s has %s hottest_cakes' % (bakery, len(bakery.hottest_cakes)) 

Si estás usando PostGreSQL, puedes usar la interfaz de Django para DISTINCT ON :

 recent_cakes = Cake.objects.order_by('bakery__id', '-baked_at').distinct('bakery__id') 

Como dicen los documentos , debe order by los mismos campos en los que distinct on . Como Simon señaló a continuación, si quieres hacer una clasificación adicional, tendrás que hacerlo en el espacio Python.

Esto debería hacer el trabajo:

 from django.db.models import Max Bakery.objects.annotate(Max('cake__baked_at')) 

Estaba luchando con un problema similar y finalmente llegué a la siguiente solución. No se basa en order_by y distinct por lo que puede ordenarse según lo deseado en el lado de la base de datos y también puede usarse como consulta anidada para el filtrado. También creo que esta implementación es independiente del motor db, porque se basa en la cláusula HAVING SQL estándar. El único inconveniente es que devolverá varios pasteles más calientes por panadería, si se hornean en esa panadería exactamente al mismo tiempo.

 from django.db.models import Max, F Cake.objects.annotate( # annotate with MAX "baked_at" over all cakes in bakery latest_baketime_in_bakery=Max('bakery__cake_set__baked_at') # compare this cake "baked_at" with annotated latest in bakery ).filter(latest_baketime_in_bakery__eq=F('baked_at')) 
 Cake.objects.filter(bakery__town="Anytown").order_by("-created_at")[:1] 

No he construido los modelos en mi extremo, pero en teoría esto debería funcionar. Desglosado:

  • Cake.objects.filter(bakery__town="Anytown") Debe devolver cualquier pastel que pertenezca a “Anytown”, asumiendo que el país no es parte de la cadena. Los dobles guiones bajos entre la bakery y la town nos permiten acceder a la propiedad de la town de la bakery .
  • .order_by("-created_at") ordenará los resultados por su fecha de creación, la más reciente primero (tome nota del signo - (menos) en "-created_at" . Sin el signo de menos, los ordenaría el más antiguo al más reciente.
  • [:1] al final devolverá solo el primer elemento de la lista que se devuelve (que sería una lista de pasteles de Anytown, ordenados por el más reciente primero).

Nota: Esta respuesta es para Django 1.11. Esta respuesta se modificó de las consultas que se muestran aquí en Django 1.11 Docs .