быстрый поиск последнего элемента в Django QuerySet? - PullRequest
10 голосов
/ 05 декабря 2009

У меня есть модель под названием Valor. У доблести есть робот. Я спрашиваю так:

Valor.objects.filter(robot=r).reverse()[0]

, чтобы получить последнего Доблести робота. Valor.objects.filter (robot = r) .count () составляет около 200000, а получение последних предметов занимает около 4 секунд на моем ПК.

Как я могу ускорить это? Я неправильно спрашиваю?

Ответы [ 9 ]

8 голосов
/ 30 ноября 2011

Оптимальный синтаксис mysql для этой проблемы будет выглядеть примерно так:

SELECT * FROM table WHERE x=y ORDER BY z DESC LIMIT 1

Эквивалент Джанго этого будет:

Valor.objects.filter(robot=r).order_by('-id')[:1][0]

Обратите внимание, как в этом решении используется метод нарезки django для ограничения набора запросов до составления списка объектов.

7 голосов
/ 05 декабря 2009

Если ни одно из предыдущих предложений не сработало, я бы предложил исключить Django из уравнения и запустить этот необработанный 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 похож на «необработанный» запрос, который я разместил выше. Вы также можете заставить свою СУБД объяснить вам этот план запроса.

3 голосов
/ 16 июля 2014

django 1.6 представляет .first () и .last ():

https://docs.djangoproject.com/en/1.6/ref/models/querysets/#last

Так что вы можете просто сделать:

Valor.objects.filter(robot=r).last()
3 голосов
/ 05 декабря 2009

Похоже, ваш набор данных будет достаточно большим, чтобы вы могли немного денормализовать вещи. Вы пытались отслеживать последний объект Доблести в объекте Робота?

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 будет происходить быстро. Поиграйте с ним и посмотрите, стоит ли компромисс для вашего приложения.

3 голосов
/ 05 декабря 2009

Ну, здесь нет порядка order_by, поэтому мне интересно, что вы подразумеваете под словом «последний». Предполагая, что вы имели в виду «последний добавленный»,

Valor.objects.filter(robot=r).order_by('-id')[0]

может сделать всю работу за вас.

1 голос
/ 19 мая 2012

Довольно быстро также должно быть:

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-запроса;)

0 голосов
/ 09 февраля 2015
Model_Name.objects.first()

// Для получения первого элемента

Model_name.objects.last()

// Для получения последнего ()

в моем случае последний не работает, потому что в базе данных есть только одна строка может помочь вам тоже:)

0 голосов
/ 13 марта 2014

Правильный способ сделать это - использовать встроенный метод QuerySet latest () и передавать его в любой столбец (имя поля), по которому он должен сортироваться. Недостатком является то, что он может сортировать только по одному столбцу БД.

Текущая реализация выглядит следующим образом и оптимизирована в том же смысле, что и предложение @ Аарона.

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()
0 голосов
/ 05 декабря 2009

Есть ли в django предельная оговорка? Таким образом, вы можете получить БД, просто вернув одну запись.

mysql

 select * from table where x = y limit 1

sql server

 select top 1 * from table where x = y

оракул

 select * from table where x = y and rownum = 1

Я понимаю, что это не переведено на django, но кто-то может вернуться и почистить это.

...