У меня есть две модели в Django, одна для песен, одна для альбомов, в альбоме много песен.Я пытаюсь отфильтровать альбомы, где песни действительны.Например, как минимум одна песня должна иметь аудиофайл, чтобы альбом мог быть возвращен фильтром.Я использую Postgres.
Я пытаюсь выяснить, как сделать эту логику с помощью Django QuerySet, но я не уверен, как использовать там, где существует, а не существует.
Ниже приводитсяОператор Django orm, который я пытаюсь заставить работать:
valid_songs = Song.objects.filter(
album=OuterRef('pk'),
audio_file__isnull=False).only("album")
Album.objects.annotate(
valid_song=Exists(valid_songs)).filter(
valid_song=True).query
Это сгенерированный запрос:
SELECT "api_album"."id",
"api_album"."created_at",
"api_album"."updated_at",
"api_album"."title",
"api_album"."artwork_file_id",
"api_album"."user_id",
"api_album"."description",
"api_album"."tags",
"api_album"."genres",
EXISTS(SELECT U0."id",
U0."album_id"
FROM "api_song" U0
WHERE ( U0."album_id" = ( "api_album"."id" )
AND U0."audio_file_id" IS NOT NULL )) AS "valid_song"
FROM "api_album"
WHERE EXISTS(SELECT U0."id",
U0."album_id"
FROM "api_song" U0
WHERE ( U0."album_id" = ( "api_album"."id" )
AND U0."audio_file_id" IS NOT NULL )) = true
Это план запроса postgres для вышеуказанного запроса, сгенерированного Django.QuerySet:
Seq Scan on api_album (cost=0.00..287.95 rows=60 width=641)
Filter: (alternatives: SubPlan 3 or hashed SubPlan 4)
SubPlan 3
-> Seq Scan on api_song u0_2 (cost=0.00..1.54 rows=1 width=0)
Filter: ((audio_file_id IS NOT NULL) AND (album_id = api_album.id))
SubPlan 4
-> Seq Scan on api_song u0_3 (cost=0.00..1.43 rows=10 width=4)
Filter: (audio_file_id IS NOT NULL)
SubPlan 1
-> Seq Scan on api_song u0 (cost=0.00..1.54 rows=1 width=0)
Filter: ((audio_file_id IS NOT NULL) AND (album_id = api_album.id))
SubPlan 2
-> Seq Scan on api_song u0_1 (cost=0.00..1.43 rows=10 width=4)
Filter: (audio_file_id IS NOT NULL)
(14 rows)
Тем не менее, есть гораздо более эффективный запрос для этого
SELECT *
FROM "api_album"
WHERE EXISTS(SELECT U0."id",
U0."album_id"
FROM "api_song" U0
WHERE ( U0."album_id" = ( "api_album"."id" )
AND U0."audio_file_id" IS NOT NULL ))
Hash Semi Join (cost=1.55..13.26 rows=10 width=640)
Hash Cond: (api_album.id = u0.album_id)
-> Seq Scan on api_album (cost=0.00..11.20 rows=120 width=640)
-> Hash (cost=1.43..1.43 rows=10 width=4)
-> Seq Scan on api_song u0 (cost=0.00..1.43 rows=10 width=4)
Filter: (audio_file_id IS NOT NULL)
(6 rows)
Поэтому мои вопросы таковы:
- В чем разница между существованием и существованием в этом сценарии, и почему не создаются одни и те же планы запросов?
- Как мне заставить Django ORM сгенерировать более эффективный запрос?
Редактировать: модели django следующие:
class Album(BaseModel):
title = models.CharField(max_length=255, blank=False)
artwork_file = models.ForeignKey(
S3File, null=True, on_delete=models.CASCADE,
related_name="album_artwork_file")
user = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name="albums",
on_delete=models.CASCADE)
description = models.TextField(blank=True)
tags = ArrayField(models.CharField(
max_length=16), default=default_arr)
genres = ArrayField(models.CharField(
max_length=16), default=default_arr)
class Song(BaseModel):
title = models.CharField(max_length=255, blank=False)
album = models.ForeignKey(Album,
related_name="songs",
on_delete=models.CASCADE)
audio_file = models.ForeignKey(
S3File, null=True, on_delete=models.CASCADE,
related_name="song_audio_file")
следующее НЕ работает, потому что если вы используете get () для этого QuerySet, оно выдаст исключение
Album.objects.filter(songs__audio_file__isnull=False).get(pk=1)
Album.MultipleObjectsReturned: get() returned more than one Album
Набор запросов используется в режиме DjangoRest.lViewSet, где набор запросов используется для операций crud и передается в сериализатор альбомов.Это требует, чтобы get () работал и возвращал одно значение.
class AlbumViewSet(viewsets.ModelViewSet):
serializer_class = AlbumSerializer
def get_queryset(self):
valid_songs = Song.objects.filter(
album=OuterRef('pk'),
audio_file__isnull=False).only('album')
# Slow query posted above
return Album.objects.annotate(
valid_song=Exists(valid_songs)
).filter(valid_song=True)