быстрый поиск последнего элемента в объект 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 тоже:)