Создание пользовательских присоединений в Django

Я пытаюсь создать правильное поведение предварительной выборки в Django. Вот в общих чертах суть проблемы:

  • У каждого счета есть ежедневные котировки, обновляемые ежедневно в разное время (представьте себе моментальный снимок)
  • Необходимо запросить все эти DailyQuotes и получить только самые последние котировки для каждого счета
  • .

Вот модели:

class Account(models.Model):
    name = models.TextField(default="")
    ...

class DailyQuotes(models.Model):
    account = models.ForeignKey(Account, related_name="quote", on_delete=models.CASCADE)
    date = models.DateField(default=None)
    ...

В настоящее время запрос внутри моего представления для этого выглядит так:

acc_ids = [1,2,3]
max_date = DailyQuotes.objects.aggregate(Max("date"))["date__max"]
accounts = (
            Account.objects.filter(id__in=acc_ids)
            .prefetch_related(
                Prefetch(
                    "quote",
                    queryset=DailyQuotes.objects.filter(date=date),
                ),
            )
        )
# Feed into serializer, etc

Это работает и генерирует 3 запроса: 1 для максимальной даты, 1 для счетов и 1 для котировок. Проблема с этим решением заключается в том, что если один счет имеет более свежие DailyQuotes, то другие счета не вернут котировки. Поэтому мне нужно получить последние DailyQuotes для каждого счета на основе максимальной даты для этого счета, а не для всех счетов.

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

Текущий предварительный запрос (генерируется Django):

SELECT ... FROM dailyquotes
    WHERE (dailyquotes.date = 2022-05-05
           AND dailyquotes.account_id IN (1,2,3))

Требуемый предварительный запрос (или аналогичный):

SELECT ... FROM dailyquotes dq
    JOIN (SELECT account_id, MAX(date) AS date__max FROM dailyquotes
          WHERE account_id in (1,2,3) group by account_id) dates 
    ON dq.account_id = dates.account_id AND dq.date = dates.date__max

Любая помощь будет очень признательна!

Если вы не привязаны к prefetch_related, вы можете сделать это в Django через DailyQuotes за 2 вызова - 1 для сбора максимальных дат и 1 для конечного набора записей (даже используя select_related, если вам нужна сопутствующая информация о счете).

from django.db.models import Max

#define lists
acc_ids = [0,1,2]
max_dates = []
recordsets = []
final_recordset = []

#get the max date for each account ID
max_dates = DailyQuotes.objects.filter(account_id__in=acc_ids).values('account_id').annotate(max_date = Max('date'))

#get the recordsets
for max_date in max_dates:
    qs = DailyQuotes.objects.filter(account_id = max_date['account_id'], date = max_date['max_date'] )
    #qs =  DailyQuotes.objects.filter(account_id = max_date['account_id'], date = max_date['max_date']).select_related('account') if you need associated account info
    recordsets.append(qs)
    
#combine the recordsets for serialising  - you may want to modify this based on length of recordsets list (in case of empty accounts) for robustness  
final_recordset = recordsets[0].union( recordsets[1], recordsets[2])
Вернуться на верх