【问题标题】:How to implement Where Exists in Django?如何在 Django 中实现 Where Exists?
【发布时间】:2019-07-25 01:35:53
【问题描述】:

我在 Django 中有两个模型,一个用于歌曲,一个用于专辑,一个专辑有很多歌曲。我正在尝试过滤歌曲有效的专辑。例如,至少一首歌曲必须有一个音频文件才能让过滤器返回专辑。我正在使用 Postgres。

我试图弄清楚如何通过 Django QuerySet 执行此逻辑,但我不确定如何使用 where exists 而不是 exists。

以下是我要开始工作的 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)

所以我的问题如下:

  1. 在这种情况下,where exists 与 exists 之间有什么区别?为什么不创建相同的查询计划?
  2. 如何让 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")

以下内容不起作用,因为如果您在此 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)

【问题讨论】:

    标签: sql django postgresql


    【解决方案1】:

    我不确定您为什么要执行这些查询。查找至少有一首歌曲有音频文件的专辑可以简单地表达为:

    Album.objects.filter(song__audio_file__isnull=False)
    

    【讨论】:

    • 这不起作用,因为专辑的外键在歌曲上。
    • 歌曲不是专辑中的字段,这将产生异常 django.core.exceptions.FieldError: Cannot resolve keyword 'song' into field.
    • 查看编辑,但我认为您试图建议 Album.objects.filter(songs__audio_file__isnull=False) 但这会在与 get() 一起使用时导致异常
    • 啊,你已经设置了related_name,所以你需要使用它:songs__audio_file__isnull
    • 是的,那你为什么要使用get?你想要多张专辑。使用filter
    猜你喜欢
    • 1970-01-01
    • 2020-01-28
    • 2020-06-22
    • 1970-01-01
    • 1970-01-01
    • 2016-02-19
    • 2018-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多