Эквивалент Django для последней записи для каждого пользователя

Я удивлен, что этот вопрос не возник. Не удалось найти много в Интернете.

Использование Entry.objects.latest('created_at') Я могу восстановить последнюю запись для всех объектов Entry, но скажите, хочу ли я, чтобы последняя запись для каждого пользователя? Это похоже на запрос последней записи SQL. Но как мне добиться этого с помощью ORM? Вот мой подход. Мне интересно, является ли это самым эффективным способом делать то, что я хочу.

Сначала я выполняю вспомогательный запрос: объекты сгруппированы по пользователю, а для каждого пользователя возвращается максимальное (последнее) созданное_by поле (created_at__max). Затем я фильтрую объекты Entry на основе результатов в подзапросе и получаю требуемые объекты.

Entry.objects.filter(created_at__in=Entry.objects.values('user').annotate(Max('created_at')).values_list('created_at__max'))

или с помощью менеджера:

class UsersLatest(models.Manager):  

    def get_query_set(self):
        return super(UsersLatest,self).get_query_set().filter(created_at__in=self.model.objects.values('user').annotate(Max('created_at')).values_list('created_at__max'))

Есть ли более эффективный способ? возможно без суб-запроса?

Спасибо,

Пол

Ответ 1

Используя order_by и distinct:

Entry.objects.all().order_by('user', 'created_at').distinct('user')

Затем для добавления индекса производительности вместе в поля "user" и "created_at".

Но я считаю, что реальный способ производства - использовать Redis для кэширования и обновления списка идентификаторов последних записей пользователей.

Ответ 2

Конструкция вашего QuerySet зависит от того, для чего вы планируете использовать его. Я не уверен, почему вы выходите из итератора QuerySet с помощью метода values_list в конце. Я полагаю, у вас есть список статусов пользователей, где вы показываете последнее время активности на основе этой модели "Записи". Для этого вы можете попробовать следующее:

Users.objects.all().annotate(latest_activity=Max('entries__created_at'))

И затем прокрутите своих пользователей легко в своем шаблоне с помощью

{% for user in users %}
{{ user.full_name }}
{{ user.latest_activity|date: "m/d/Y" }}
{% endfor %}

Ответ 3

Необработанный SQL был бы

SELECT entry.id, entry.title, entry.content, entry.user_id, entry.created_at
FROM
    entry
WHERE
    entry.created_at = ( SELECT Max(e2.created_at) from entry as e2 where e2.user_id = entry.user_id )

Таким образом, один параметр использует аргумент where extra() модификатор:

Entry.objects.extra(where='entry.created_at = ( SELECT Max(e2.created_at) from entry as e2 where e2.user_id = entry.user_id )')

Конечно, вам, вероятно, придется изменить entry на то, что фактическое имя таблицы находится в базе данных. Предполагая, что вам удобно смотреть на ._meta, вы можете попробовать следующее:

Entry.objects.extra( where=
    '%(table)s.created_at = ( SELECT Max(e2.created_at) from %(table)s as e2 where e2.user_id = %(table)s.user_id )' % { 'table':Entry._meta.db_table }
)

Вероятно, более элегантный способ получить имя таблицы.

Ответ 4

Я не могу придумать один необработанный SQL-запрос, который будет извлекать нужный вам набор, поэтому я сомневаюсь, что можно построить QuerySet с этими результатами.

Ответ 5

У меня была аналогичная проблема, и я сделал это следующим образом:

priorities = obj.books_set.values('category').annotate(priority=Max('priority'))

Примечание: Я отмечаю максимальный приоритет как приоритет, потому что я буду использовать его в качестве условия фильтра.

Это список категорий с минимальными приоритетами. Затем я делаю это:

>>> priorities[0]
{'category': 1, 'priority': 10}

Я хочу найти книги, у которых есть категория и приоритетная пара среди одного в списке. В конце условия запроса должны выглядеть так:

Q(priorities[0]) | Q(priorities[1]) | ...

Чтобы сделать это в одной строке, используйте reduce в Q.__or__:

reduce(Q.__or__, (Q(**x) for x in priorities))

Я знаю, это немного хуже, чем сырой SQL, но безопаснее. Прокомментируйте этот код, если вы его используете, потому что его трудно прочитать.