Какова наилучшая практика работы с несколькими агрегациями базы данных и пагинацией
У меня есть REST сервис (проект использует django и DRF), который обслуживает данные из базы данных MSSQL. Одной из моделей, хранящихся в БД, являются инвойсы, которые выглядят следующим образом:
class Invoice(models.Model):
id = models.IntegerField(primary_key=True)
customer = models.ForeignKey('project_models.Customer', on_delete=models.CASCADE,related_name='invoices')
material = models.ForeignKey('project_models.Material', on_delete=models.SET_NULL)
quantity = models.FloatField()
revenue = models.FloatField()
invoice_date = models.DateField()
Я хочу представить некоторые агрегированные данные для этих счетов-фактур по материалам, например:
- Сумма выручки за текущий год
- Сумма выручки за прошлый год
- Сумма выручки за прошлый год до сегодняшней даты (например, сегодня 2022-03-02, сумма будет с 2021-01-01 по 2021-03-01)
Для решения этой проблемы я могу выполнить запрос с минимальной агрегацией (например, сумма выручки за день) и итерацией по набору результатов для создания списка записей, который содержит всю необходимую информацию. Однако, поскольку материалов много, такое решение может привести к проблемам с производительностью. Обычно эта проблема решается путем постраничной обработки набора запросов. Поскольку я все еще выполняю выборку и вычисления, а также итерации по всем данным БД, это кажется не самым лучшим решением.
Поэтому мой вопрос: что было бы лучшим подходом для объединения нескольких данных, сохраняя при этом пагинацию.
Если вас беспокоит производительность при получении информации о счете-фактуре и его связях (клиент, материал), вы можете взглянуть на select_related и prefetch_related.
Оба направлены на повышение производительности за счет уменьшения количества обращений к базе данных.
select_related
select_related собирает в одном вызове базы данных поиск информации об объекте (в данном случае количество, выручка, ...) и о его связях. Объединение таблиц производится на уровне базы данных (фактически в SQL). Оно работает только с отношениями с иностранными ключами.
prefetch_related
prefetch_related делает один вызов для объекта (Invoice в нашем случае), и один вызов на отношение (customer и material в нашем случае). Соединение таблиц выполняется на уровне Python. Оно работает с отношениями "многие ко многим" и "многие к одному".
Выполнение
Поэтому вам следует просто переписать запрос, оставив вычисления нетронутыми (сумма выручки за текущий год, ...). Учитывая threshold_date, запрос выглядит следующим образом :
invoices = Invoice.objects.filter(invoice_date__gt=threshold_date).prefetch_related('customer', 'material')
Однако, если вас интересует только вычисление результата, а не извлечение информации об объектах (для счета, клиента или материала), я бы рекомендовал выполнять вычисления на уровне базы данных.