У нас есть пара моделей, которые выглядят (примерно), как это:Джанго Prefetch Связанные с фильтром на максимальное значение
class Machine(models.Model):
machine_id = models.CharField(max_length=10)
# Other irrelevant fields
@property
def latest_update(self):
if self.machineupdate_set.count() == 0:
return None
return self.machineupdate_set.order_by('-update_time')[:1].get()
class MachineUpdate(models.Model):
machine = models.ForeignKey(Machine)
update_time = models.DateTimeField(auto_now_add=True)
# Other irrelevant fields
Всякий раз, когда мы загружаем Machine
S из базы данных, мы всегда в конечном итоге с помощью latest_update
для этого машина. Когда мы впервые реализованный в этом, у нас было много машин и довольно небольшое количество обновлений в машину, так что для повышения производительности (за счетом уменьшения количества запросов) мы добавили простую предвыборку по умолчанию для менеджера модели для Machine
:
class MachineManager(models.Manager):
def get_queryset(self):
return super(MachineManager, self).get_queryset().prefetch_related('machineupdate_set')
Однако все изменилось, и теперь у нас есть огромное количество обновлений, связанных с каждой машиной, и запрос предварительной выборки начинает становиться проблемой (как с точки зрения длительного времени выполнения запроса, так и потребления памяти).
Мы ищем более разумный способ предварительной выборки требуемых данных, так как все мы действительно необходимо предварительно выбрать последнее обновление для каждой машины, не все из них. Взглянув на Django prefetch_related docs казалось, мы могли бы изменить get_queryset
в нашем MachineManager
к чему-то вроде этого:
def get_queryset(self):
latest_update_query = MachineUpdate.objects.order_by('-update_time')[:1]
latest_update_prefetch = models.Prefetch('machineupdate_set', queryset=latest_update_query, to_attr='_latest_update')
return super(MachineManager, self).get_queryset().prefetch_related(latest_update_prefetch)
, а затем изменить latest_update
использовать новый атрибут, заселенный предварительную выборку. Однако, это не работает, потому что всякий раз, когда мы фильтровать Machine
запрос с помощью этого мы получаем ошибку: AssertionError: Cannot filter a query once a slice has been taken.
Может кто-нибудь предложить решение этой проблемы, так что мы можем эффективно загрузить latest_update
для каждой машины? Мы не уверены, как исправить проблему, с которой мы столкнулись, с попыткой предварительной настройки последних обновлений.
(FYI - мы рассмотрели добавление is_latest_update
булева поля для MachineUpdate
, который мы можем фильтровать, или же внешний ключ ссылки latest_update
на Machine
, однако мы хотим, чтобы избежать необходимости поддерживать эту избыточную информацию).
Это работало сказочно - мы на самом деле в конечном итоге реализации сочетание обоих вы будете предложения, используя 'last_machineupdate_list' в объектах возвращаются определенными запросами менеджера и возвращаются к более эффективному подходу, предложенному вами в конце ответа. – robjohncox
Как отметил мой коллега, мы также смогли поместить 'objects = MachineQueryset.as_manager()' в класс 'Machine', устраняя необходимость в пустом классе MachineManager. – robjohncox
Благодарим вас за отправку этого подробного примера! У меня есть аналогичная проблема, а также подумал об использовании 'annotate' как эффективного подхода, чтобы избежать множества небольших запросов.Ваше сообщение подтверждает, что это также может быть разумным решением для моего дела. – goetz