从 Django 查询中删除多余的 INNER JOIN

Removing a superfluous INNER JOIN from a Django query

我有这样的模型来描述音乐专辑、其中的曲目以及个人收听特定曲目:

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)

要获取一张专辑中的所有曲目,按它们被听到的次数排序,我可以这样做:

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

这得到了正确的结果,但似乎效率不高。结果查询的简化版本是:

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

我可以通过失去 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

少用一个索引,速度大约减半。但我无法弄清楚如何让 Django ORM 执行此操作。 (我目前正在使用 SQLite,如果这有所不同,尽管稍后会使用 Postgresql。)

如果你.filter拳头和.annotate之后,你的JOIN将被重用

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

将导致

SELECT 
    "music_track"."id",
    "music_track"."name",
    COUNT(DISTINCT "music_listen"."id") AS "listen_count" 
FROM "music_track" 
INNER JOIN "music_listen" ON ("music_track"."id" = "music_listen"."track_id") 
 WHERE "music_listen"."album_id" = 1294 
GROUP BY "music_track"."id", "music_track"."name" 
ORDER BY "listen_count" DESC