Django предварительная выборка и выбор связанных - PullRequest
0 голосов
/ 23 января 2020

У меня проблемы с пониманием prefetch_related и select_related в Django ORM. У меня есть следующие модели:

class City(models.Model):
    name = models.CharField(max_length=35)
    state = models.ForeignKey('states.State', on_delete=models.CASCADE)

class Street(models.Model):
    name = models.CharField(max_length=35)
    building = models.ForeignKey('buildings.Building', on_delete=models.CASCADE)
    city = models.ForeignKey('cities.City', on_delete=models.CASCADE)

А теперь мой views.py:

cities = City.objects.all()
streets = Street.objects.all()

for city in cities: 
    has_bank = streets.filter(building_id=1, city=city)
    if has_bank:
        city.has_bank = 1

    has_cinema = streets.filter(building_id=2, city=city)
    if has_cinema:
        city.has_cinema = 1

    has_church = streets.filter(building_id=3, city=city)
    if has_church:
        city.has_church = 1

Но теперь он попадает в базу данных 3 раза каждый раз, когда для l oop повторяется. Я пытаюсь улучшить временную сложность - которая теперь 3N + 2 , где N - количество городов, но я не могу понять select_related и prefetch_related.

Можете ли вы привести пример, как бы я мог улучшить это, чтобы он не попадал в базу данных 3 раза в течение l oop?

Ответы [ 2 ]

2 голосов
/ 23 января 2020

В вашем конкретном случае c я предполагаю, что лучше использовать аннотацию вместо предварительной выборки:

from django.db.models import Count, Q

cities = City.objects
.annotate(bank_count=Count("street", filter=Q(street__building_id=1)))
.annotate(cinema_count=Count("street", filter=Q(street__building_id=2)))
.annotate(church_count=Count("street", filter=Q(street__building_id=3)))

Теперь вы можете напрямую использовать bank_count, cinema_count и church_count атрибуты:

for city in cities: 
   print(city.bank_count)
   print(city.cinema_count)
   print(city.church_count)

Если вы хотите использовать prefetch_related, вам нужно использовать Prefetch объект. Это позволяет фильтровать предварительно выбранные объекты:

City.objects.prefect_related(
    Prefetch("street_set", queryset=Street.objects.filter(building_id=1), to_attr='has_bank'),
    Prefetch("street_set", queryset=Street.objects.filter(building_id=2), to_attr='has_cinema'),
    Prefetch("street_set", queryset=Street.objects.filter(building_id=3), to_attr='has_church')
)

Примечание. Аргумент to_attr позволяет предварительно выбирать объекты одной модели с разными фильтрами для разных атрибутов. Так что вы можете сделать сейчас:

for city in cities: 
   print(city.has_bank)
   print(city.has_cinema)
   print(city.has_church)
1 голос
/ 23 января 2020

Выберите связанный. Позвольте мне объяснить немного. Я добавил фиктивные данные для объяснения.

class City(models.Model):
    name = models.CharField(max_length=35)
    state = models.ForeignKey('states.State', on_delete=models.CASCADE)

class Street(models.Model):
    name = models.CharField(max_length=35)
    building = models.ForeignKey('buildings.Building', on_delete=models.CASCADE)
    city = models.ForeignKey('cities.City', on_delete=models.CASCADE)

Ваша таблица городов должна быть такой.

id    name         state
1   Cityname1      state1  
2   Cityname2        2

Ваша таблица улиц должна быть.

id  name   city ..
1   st 1    1
2   stno.2  1
3   st no3  2

Если ваш запрос orm будет таким.

street = Street.objects.select_related('city')

Этот запрос объединяет две таблицы как одну. означает, что все внешние ключи с идентификатором города будут присоединяться к каждому идентификатору улицы для создания новой таблицы следующим образом. Он вернет три записи, потому что мы используем выбранный связанный город, и основной таблицей в этом случае является улица, поэтому он вернет 3 записи. Основная таблица во всех случаях будет возвращаться первой в дайне go.

 id   name   city ..  city.id  city.name  city.state
  1   st 1    1         1      Cityname1   state1
  2   stno.2  1         1      Cityname1   state1
  3   st no3  2         2      Cityname2    2
...