我在Django中有两个模型,一个用于歌曲,一个用于专辑,一个专辑有很多歌曲。我正在尝试过滤歌曲有效的专辑。例如,至少一首歌曲必须具有音频文件,以便过滤器返回专辑。我正在使用Postgres。
我试图弄清楚如何通过Django QuerySet来执行此逻辑,但是我不确定如何使用where存在而不存在。
以下是我要开始使用的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
这是由Django的QuerySet生成的上述查询的postgres查询计划:
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模型如下:
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")
以下操作无效,因为如果在此QuerySet上使用get(),它将抛出异常
Album.objects.filter(songs__audio_file__isnull=False).get(pk=1)
Album.MultipleObjectsReturned: get() returned more than one Album
该查询集与DjangoRest ModelViewSet一起使用,该查询集用于Crud操作,并传递给Album Serializer。这需要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)
答案 0 :(得分:1)
我不确定您为什么要执行这两个查询。查找专辑中至少有一首歌曲带有音频文件的过程表示为:
>>> s = []
>>> with open('f.txt') as file_in:
... s = file_in.read().split()
...
>>> s
['Hello', 'Bye', 'Good']