Как реализовать, где существует в Django? - PullRequest
0 голосов
/ 03 марта 2019

У меня есть две модели в 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)

Поэтому мои вопросы таковы:

  1. В чем разница между существованием и существованием в этом сценарии, и почему не создаются одни и те же планы запросов?
  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")

следующее НЕ работает, потому что если вы используете 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)

1 Ответ

0 голосов
/ 03 марта 2019

Я не уверен, почему вы делаете любой из этих запросов.Поиск альбомов, в которых хотя бы у одной песни есть аудиофайл, выражается просто:

Album.objects.filter(song__audio_file__isnull=False)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...