Cómo optimizar una ordenación basada en el modelo relacionado “más reciente”

Así que digamos que tenemos dos modelos.

class Product(models.Model): """ A model representing a product in a website. Has new datapoints referencing this as a foreign key daily """ name = models.CharField(null=False, max_length=1024, default="To be Scraped") url = models.URLField(null=False, blank=False, max_length=10000) class DataPoint(models.Model): """ A model representing a datapoint in a Product's timeline. A new one is created for every product daily """ product = models.ForeignKey(Product, null=False) price = models.FloatField(null=False, default=0.0) inventory_left = models.BigIntegerField(null=False, default=0) inventory_sold = models.BigIntegerField(null=False, default=0) date_created = models.DateField(auto_now_add=True) def __unicode__(self): return "%s - %s" % (self.product.name, self.inventory_sold) 

El objective es ordenar un QuerySet de productos según el valor de Inventory_sold del último punto de datos adjunto al producto. Esto es lo que tengo hasta ahora:

 products = Product.objects.all() datapoints = DataPoint.objects.filter(product__in=products) datapoints = list(datapoints.values("product__id", "inventory_sold", "date_created")) products_d = {} # Loop over the datapoints values array for i in datapoints: # If a datapoint for the product doesn't exist in the products_d, add the datapoint if str(i["product__id"]) not in products_d.keys(): products_d[str(i["product__id"])] = {"inventory_sold": i["inventory_sold"], "date_created": i["date_created"]} # Otherwise, if the current datapoint was created after the existing datapoint, overwrite the datapoint in products_d else: if products_d[str(i["product__id"])]["date_created"] < i["date_created"]: products_d[str(i["product__id"])] = {"inventory_sold": i["inventory_sold"], "date_created": i["date_created"]} # Sort the products queryset based on the value of inventory_sold in the products_d dictionary products = sorted(products, key=lambda x: products_d.get(str(x.id), {}).get("inventory_sold", 0), reverse=True) 

Esto funciona bien, pero es extremadamente lento con un alto número (500,000 ~) de productos y puntos de datos. ¿Hay alguna manera mejor de hacer esto?

Y en una nota al margen (no importante), ya que no he podido encontrar nada sobre esto, parece que el método Unicode del modelo DataPoint también está realizando consultas SQL innecesarias. ¿Es esto algo que es una característica predeterminada de los modelos de Django una vez que se pasan a las plantillas?

Creo que puede usar una subconsulta aquí para anotar el valor del punto de datos más reciente, y luego ordenar eso.

Basado en el ejemplo de esos documentos, sería algo como:

 from django.db.models import OuterRef, Subquery newest = DataPoint.objects.filter(product=OuterRef('pk')).order_by('-date_created') products = Product.objects.annotate( newest_inventory_sold=Subquery(newest.values('inventory_sold')[:1]) ).order_by('newest_inventory_sold') 

Para su punto de vista lateral, para evitar consultas adicionales al select_related de datos, select_related utilizar select_related en la consulta original:

 datapoints = DatePoint.objects.filter(...).select_related('product') 

Esto hará una ÚNICA para que obtener el nombre del producto no cause una nueva búsqueda de db.