Django ORM Фильтрация внутри функций БД, возвращающих несколько повторяющихся строк - PullRequest
2 голосов
/ 20 апреля 2020

Я должен перечислить всех клиентов и прокомментировать их общую сумму кредита и платежей внутри одного запроса sql. Я попробовал это со следующим фрагментом кода:

clients = Client.objects.all().order_by('fullname').annotate(
    loans_total=ArrayAgg(
        'sales__loan',
        filter=Q(Q(sales__created__gte=start_date) & Q(sales__created__lte=end_date))
    ),
    payments_total=ArrayAgg(
        Case(
            When(payments__amount_currency="SUM", then=F('payments__amount') / F('payments__rate')),
            default=F('payments__amount'),
            output_field=FloatField()
        ),
        filter=Q(payments__created__gte=start_date) & Q(payments__created__lte=end_date)
    ),
)

, но он возвращает несколько дублированных записей строк, и результат их суммы умножается на 30 в этом случае;

ОБНОВЛЕНИЕ # 1

class Client(BaseSoftDeletableModel):

    fullname = models.CharField(
            max_length=100,

    )
    loan = models.FloatField(
            default=0,

    )
    referal = models.ForeignKey(
            'self',
            on_delete=models.CASCADE,
            null=True,
            blank=True,

    )
    price_type = models.ForeignKey(
            'core.PriceType',
            on_delete=models.SET_NULL,
            null=True,
            blank=True,

    )
    shop = models.ForeignKey(
            'core.Shop',
            on_delete=models.SET_NULL,
            null=True,
            blank=True
    )

Вот модель прибыли

class Profit(models.Model):
    employee = models.ForeignKey(
        'staff.Employee',
        on_delete=models.CASCADE,
        null=True,
    )
    source = models.ForeignKey(
        ProfitSource,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
    )
    cashbox = models.ForeignKey(
        'pos.CashBox',
        on_delete=models.CASCADE,
        related_name='profits',
    )
    from_cashbox = models.ForeignKey(
        'pos.CashBox',
        on_delete=models.CASCADE,
        related_name='out_expenses',
        null=True,
        blank=True,
    )
    created = models.DateTimeField(
        default=timezone.now,
    )
    convert = models.BooleanField(
        default=False,
    )
    profit_type = models.CharField(
        max_length=5,
        choices=TYPES,
        default=SALE_PROFIT,
    )
    amount = models.DecimalField(
        max_digits=16,
        decimal_places=2,
    )
    amount_currency = models.CharField(
        max_length=10,
        choices=settings.CURRENCY_CHOICES,
        default=settings.CURRENCY_CHOICES[0][0],
    )
    note = models.CharField(
        max_length=500,
        null=True,
        blank=True,
    )
    client = models.ForeignKey(
        'pos.Client',
        on_delete=models.CASCADE,
        related_name='payments',
        null=True,
        blank=True,
    )
    sale = models.ForeignKey(
        'pos.Sale',
        on_delete=models.CASCADE,
        related_name='profits',
        null=True,
        blank=True,
    )
    rate = models.FloatField(
        default=0,
    )
    state = FSMField(
        choices=STATES,
        default=STATE_UNDONE,
        max_length=16,
    )
    done = models.BooleanField(
        default=False,
    )

Вот структура моих моделей

1 Ответ

2 голосов
/ 30 апреля 2020

Из того, что я могу узнать из вашего запроса, вы пытаетесь создать несколько агрегаций (ArrayAgg), которые создают поведение в результатах. Вы найдете более подробную информацию здесь https://docs.djangoproject.com/en/3.0/topics/db/aggregation/#combining -multiple-aggregations

Чтобы избежать этого, вы должны использовать подзапросы:

clients = Client.objects.order_by('fullname').annotate(
    loans_total=Subquery(
        Sale.objects.filter(
            created__gte=start_date,
            created__lte=end_date,
            client=OuterRef('pk'),
        ).annotate(loan_sum=Sum('loan')).values('loan_sum')[:1]
    ),
    payments_total=Subquery(
        Profit.objects.filter(
            created__gte=start_date,
            created__lte=end_date,
            client=OuterRef('pk'),
        ).annotate(
            payment_amount=Case(
                When(amount_currency="SUM", then=F('amount') / F('rate')),
                default=F('amount'),
                output_field=FloatField(),
            ),
            payment_total=Sum('payment_amount'),
        ).values('payment_total')[:1]
    ),
)

Теперь я У меня нет структуры вашей модели "pos.Sale", но я надеюсь, что вы выбрали правильный путь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...