Оптимизация запроса Django - PullRequest
       6

Оптимизация запроса Django

0 голосов
/ 02 октября 2018

У меня есть 2 модели, подобные этим:

class Client(models.Model):
  // some fields

class Transaction(models.Model):
  client = models.ForeignKey(Client)
  created = models.DateTimeField(auto_now_add=True)
  amount = DecimalField(max_digits=9, decimal_places=2)

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

Например, если у меня есть такой набор данных и что date имеет значение 01/20:

Client1:
- Transaction 1, created on 01/15, 5€
- Transaction 2, created on 01/16, 6€
- Transaction 3, created on 01/22, 7€

Client2:
- Transaction 4, created on 01/18, 8€
- Transaction 5, created on 01/19, 9€
- Transaction 6, created on 01/21, 10€

Client3:
- Transaction 7, created on 01/21, 11€

Тогда запрос должен вернуть 15 (6 € + 9 € от транзакций 2 и 5).

С точки зрения производительности, моя цель - избежать N запросов для N клиентов.

В настоящее время у меня проблемы с выбором правильных Transaction объектов.Может быть, я мог бы начать с: Transaction.objects.filter(created__lt=date).select_related('client').Но тогда я не могу понять, как выбрать только самые последние для каждого клиента .

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

Взгляните на документы Django по агрегации , использованию Sum , выражений SubQuery и QuerySet.values ​​() ,С их помощью мы можем создать один запрос через ORM, чтобы получить то, что вам нужно, позволяя базе данных выполнять всю работу:

from django.db.models import Sum, Subquery, OuterRef
from django.utils import timezone

from . import models


# first, start with the client list, rather than the transaction list    
aggregation = models.Client.objects.aggregate(
    # aggregate the sum of our per client sub queries
    result=Sum(
        Subquery(
            models.Transaction.objects.filter(
                # filter transactions by outer query's client pk
                client=OuterRef('pk'),
                created__lt=timezone.datetime(2018, 1, 20),
            )
            # order descending so the transaction we're after is first in the list
            .order_by('-created')
            # use QuerySet.values() to grab just amount and slice the queryset
            # to limit the subquery result to a single transaction for each client
            .values('amount')[:1]
        )
    )
)
# aggregation == {'result': Decimal('15.00')}
0 голосов
/ 02 октября 2018

Что-то вроде следующего должно сделать трюк, который использует последний метод QuerySet Джанго

total = 0
for client in clients
    try:    
        total += Transactions.filter(client = client).filter(created__lt = date).latest('created').amount
    except DoesNotExist:
         pass
...