быстрый поиск последнего элемента в объект QuerySet в Django?
У меня есть модель под названием Valor. У валора есть робот. Я спрашиваю так:
Valor.objects.filter(robot=r).reverse()[0]
чтобы получить последнюю доблесть робота r. Доблесть.объекты.фильтр(робот=Р).count () составляет около 200000, и получение последних элементов занимает около 4 секунд на моем компьютере.
Как я могу ускорить его? Я запрос неправильно?
9 ответов
похоже, что ваш набор данных будет достаточно большим, чтобы вы могли немного денормализовать вещи. Вы пробовали отслеживать последний объект Доблести в объекте робота?
class Robot(models.Model):
# ...
last_valor = models.ForeignKey('Valor', null=True, blank=True)
а затем используйте post_save сигнал сделать обновление.
from django.db.models.signals import post_save
def record_last_valor(sender, **kwargs):
if kwargs.get('created', False):
instance = kwargs.get('instance')
instance.robot.last_valor = instance
post_save.connect(record_last_valor, sender=Valor)
вы оплатите стоимость дополнительной транзакции БД при создании объектов Valor, но поиск last_valor будет быстрым. Поиграйте с ним и посмотрите, если компромисс стоит для вашего приложения.
оптимальным синтаксисом mysql для этой проблемы было бы что-то вроде:
SELECT * FROM table WHERE x=y ORDER BY z DESC LIMIT 1
эквивалент django этого будет:
Valor.objects.filter(robot=r).order_by('-id')[:1][0]
обратите внимание, как это решение использует для нарезки метод ограничения queryset до составление списка объектов.
Если ни одно из предыдущих предложений не работает, я бы предложил взять Django из уравнения и запустить этот raw sql против вашей базы данных. Я угадываю имена ваших таблиц, поэтому вам, возможно, придется соответствующим образом настроить:
SELECT * FROM valor v WHERE v.robot_id = [robot_id] ORDER BY id DESC LIMIT 1;
- Это что медленно? Если да, сделайте свою СУБД (MySQL?) объясните вам план запроса. Это скажет вам, делает ли он полное сканирование таблицы, которое вы, очевидно, не хотите с такой большой таблицей. Вы также можете отредактировать свой вопрос и включить схему для valor
таблица для нас, чтобы увидеть.
кроме того, вы можете увидеть SQL, который генерирует Django, делая это (используя набор запросов, предоставленный Питером Роуэллом):
qs = Valor.objects.filter(robot=r).order_by('-id')[0]
print qs.query
убедитесь, что SQL похож на "сырой" запрос, который я опубликовал выше. Вы также можете заставить свои СУБД объяснить этот план запроса.
ну, нет предложения order_by, поэтому мне интересно, что вы подразумеваете под "последним". Предполагая, что вы имели в виду "последний добавленный",
Valor.objects.filter(robot=r).order_by('-id')[0]
может сделать работу для вас.
django 1.6 представляет .first () and .last ():
https://docs.djangoproject.com/en/1.6/ref/models/querysets/#last
таким образом, вы можете просто сделать:
Valor.objects.filter(robot=r).last()
довольно быстро также должно быть:
qs = Valor.objects.filter(robot=r) # <-- it doesn't hit the database
count = qs.count() # <-- first hit the database, compute a count
last_item = qs[ count-1 ] # <-- second hit the database, get specified rownum
таким образом, на практике вы выполняете только 2 SQL-запроса;)
есть ли ограничение в django? Таким образом, вы можете иметь db, просто вернуть одну запись.
в MySQL
select * from table where x = y limit 1
sql server
select top 1 * from table where x = y
oracle
select * from table where x = y and rownum = 1
Я понимаю, что это не переведено на django, но кто-то может вернуться и очистить это.
правильный способ сделать это-использовать встроенный метод QuerySet latest() и подавать его в любой столбец (имя поля), по которому он должен сортировать. Недостатком является то, что он может сортировать только по одному столбцу db.
текущая реализация выглядит так и оптимизирована в том же смысле, что и предложение @Aaron.
def latest(self, field_name=None):
"""
Returns the latest object, according to the model's 'get_latest_by'
option or optional given field_name.
"""
latest_by = field_name or self.model._meta.get_latest_by
assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
assert self.query.can_filter(), \
"Cannot change a query once a slice has been taken."
obj = self._clone()
obj.query.set_limits(high=1)
obj.query.clear_ordering()
obj.query.add_ordering('-%s' % latest_by)
return obj.get()
Model_Name.objects.first()
/ / для get first element
Model_name.objects.last()
/ / для get last ()
в моем случае last не работает, потому что в базе данных есть только одна строка может быть, помощь полная для u тоже:)