Поскольку вы используете Postgres, вы можете использовать Функции окна , которые выполняют вычисления для набора строк таблицы, которые так или иначе связаны с текущей строкой. Еще одна полезная информация заключается в том, что вы используете Django2.x, который поддерживает функции окна ( Django docs ), что позволяет добавить предложение OVER
к Querysets
.
Ваш сценарий использования может быть решен с помощью одного запроса ORM, например:
from django.db.models.expressions import Window
from django.db.models.functions import RowNumber
from django.db.models import F
results = Entry.objects.annotate(row_number=Window(
expression=RowNumber(),
partition_by=[F('client')],
order_by=F('created').desc())
).order_by('row_number', 'client')
for result in results:
print('Id: {} - client: {} - row_number {}'.format(result.id, result.client, result.row_number))
Выход:
Id: 12 - client: facebook - row_number 1
Id: 13 - client: google - row_number 1
Id: 11 - client: facebook - row_number 2
Id: 8 - client: google - row_number 2
Id: 10 - client: facebook - row_number 3
Id: 5 - client: google - row_number 3
Id: 9 - client: facebook - row_number 4
Id: 3 - client: google - row_number 4
Id: 7 - client: facebook - row_number 5
Id: 2 - client: google - row_number 5
Id: 6 - client: facebook - row_number 6
Id: 1 - client: google - row_number 6
Id: 4 - client: facebook - row_number 7
Необработанный SQL выглядит так:
SELECT
"orm_entry"."id",
"orm_entry"."name",
"orm_entry"."client",
"orm_entry"."created",
ROW_NUMBER() OVER (PARTITION BY "orm_entry"."client" ORDER BY "orm_entry"."created" DESC) AS "row_number"
FROM "orm_entry"
ORDER BY "row_number" ASC, "orm_entry"."client" ASC
Оконные функции объявляются как агрегатная функция, за которой следует предложение OVER, которое указывает, как именно группируются строки. Группа строк, к которой применяется оконная функция, называется «разбиением».
Вы можете заметить, что мы сгруппировали строки по полю 'client' , и вы можете заключить, что в нашем примере у нас будет два раздела. Первый раздел будет содержать все записи facebook , а второй раздел будет содержать все записи google . В своей базовой форме раздел ничем не отличается от обычной группы агрегатных функций: просто набор строк, считающихся «равными» по некоторым критериям, и функция будет применяться ко всем этим строкам для возврата одного результата.
В вашем примере мы можем использовать оконную функцию row_number
, которая просто возвращает индекс текущей строки в своем разделе, начиная с 1. Это помогло мне установить чередующийся вывод в order_by('row_number', 'client')
.
Дополнительная информация:
Если вы хотите выполнить такой заказ:
'facebook','facebook', 'google','google','facebook','facebook','google','google'
или
'facebook','facebook','facebook','google','google','google','facebook', 'facebook','facebook'
Вам потребуется выполнить одну небольшую математическую модификацию предыдущего запроса, например:
GROUP_SIZE = 2
results = Entry.objects.annotate(row_number=Window(
expression=RowNumber(),
partition_by=[F('client')],
order_by=F('created').desc())
).annotate(row_number=(F('row_number') - 1)/GROUP_SIZE + 1).order_by('row_number', 'client')
for result in results:
print('Id: {} - client: {} - row_number {}'.format(result.id, result.client, result.row_number))
Выход:
Id: 12 - client: facebook - row_number 1
Id: 11 - client: facebook - row_number 1
Id: 8 - client: google - row_number 1
Id: 13 - client: google - row_number 1
Id: 10 - client: facebook - row_number 2
Id: 9 - client: facebook - row_number 2
Id: 3 - client: google - row_number 2
Id: 5 - client: google - row_number 2
Id: 7 - client: facebook - row_number 3
Id: 6 - client: facebook - row_number 3
Id: 1 - client: google - row_number 3
Id: 2 - client: google - row_number 3
Id: 4 - client: facebook - row_number 4
Вы можете заметить, что константа GROUP_SIZE
определяет, сколько элементов будет в каждой чередующейся группе.
приписка
Спасибо, что задали этот вопрос, потому что он помог мне лучше понять функции окна.
Удачного кодирования:)