2016-11-17 7 views
1

I Modelle wie diese beschreiben, Musik-Alben haben, verfolgt auf sie, und einzelne hört auf bestimmte Spuren:eine überflüssige INNER aus einer Django Abfrage JOIN Entfernen

class Album(models.Model): 
    name = models.CharField(max_length=255) 

class Track(models.Model): 
    name = models.CharField(max_length=255) 

class Listen(models.Model): 
    track = models.ForeignKey('Track', related_name='listens', db_index=True) 
    album = models.ForeignKey('Album', related_name='listens', db_index=True, blank=True) 

auf einem Album, alle Titel zu erhalten, geordnet nach die Anzahl, wie oft sie gehört habe, kann ich:

Track.objects \ 
    .annotate(listen_count=models.Count('listens', distinct=True)) \ 
    .filter(listens__album=1294) \ 
    .order_by('-listen_count') 

Diese Ergebnisse bekommt richtig, aber es scheint ineffizient. Eine vereinfachte Version der resultierenden Abfrage ist:

SELECT track.id, 
     track.name, 
     COUNT(DISTINCT listen.id) AS listen_count 
FROM track 
LEFT OUTER JOIN listen ON (track.id = listen.track_id) 
INNER JOIN listen T3 ON (track.id = T3.track_id) 
WHERE T3.album_id = 1294 
GROUP BY track.id, track.name 
ORDER BY listen_count DESC 

ich die gleichen Ergebnisse durch den Verlust bekommen, dass INNER JOIN:

SELECT track.id, 
     track.name, 
     COUNT(DISTINCT listen.id) AS listen_count 
FROM track 
LEFT OUTER JOIN listen ON (track.id = listen.track_id) 
WHERE listen.album_id = 1294 
GROUP BY track.id, track.name 
ORDER BY listen_count DESC 

Das verwendet man weniger Index und etwa die Hälfte der Geschwindigkeit. Aber ich kann nicht herausfinden, wie ich das Django ORM dazu bringen kann. (Ich bin derzeit mit SQLite, wenn das einen Unterschied macht, wird, obwohl Postgresql verwenden später.)

+0

Tun Sie die Filterung 1., dann die Annotation + die Bestellung – Todor

Antwort

3

Wenn Sie .filter Faust und .annotate nach, wird Ihr JOIN

>>> qs = (Track.objects 
...  .filter(listens__album=1294) 
...  .annotate(listen_count=models.Count('listens', distinct=True)) 
...  .order_by('-listen_count') 
...) 

wiederverwendet werden zu

führen
+0

Oh, so einfach! Ich habe den Anmerkungsteil in einer Manager-Methode im Moment, also muss ich die Sachen ein wenig reorganisieren, bevor ich den Filter an die erste Stelle setzen kann, aber das sollte möglich sein. * Also * viel schneller, danke! –

+0

Falls Sie dies in der Dokumentation nicht finden, sehen Sie sich die folgenden Anweisungen an: [Order of annotate() und filter()] (https://docs.djangoproject.com/en/1.10/topics/db/aggregation/#order -of-Annotate-Filter-Klauseln) – Todor