将Django 1.8中复杂子查询的结果注释为QuerySet

eln*_*ren 0 mysql sql django django-orm django-queryset

我必须遵循数据库架构:

main_account
- username

invoice
- amount
- currency: 'EUR' or 'USD' or 'GBP'
- username (main_account has many invoices)

我的目标是以美元列出"最高支付者".因此,我必须从发票表中计算每个用户的"total_paid"总和(并考虑货币转换).我也想通过这个"total_paid"来过滤.

我当前的SQL看起来像:

SELECT main_account.username, total_paid
FROM main_account JOIN
(
    SELECT username,
        (SELECT SUM(amount) FROM invoice WHERE main_account.username = invoice.username AND invoice.currency = 'EUR') AS total_eur,
        (SELECT SUM(amount) FROM invoice WHERE main_account.username = invoice.username AND invoice.currency = 'USD') AS total_usd,
        (SELECT SUM(amount) FROM invoice WHERE main_account.username = invoice.username AND invoice.currency = 'GBP') AS total_gbp,
        (SELECT (IFNULL(total_usd,0) + 1.12 * IFNULL(total_eur,0) + 1.41 * IFNULL(total_gbp,0))) AS total_paid
    FROM main_account
) as tbl
ON main_account.username = tbl.username
WHERE total_paid >= 2000
ORDER BY total_paid
Run Code Online (Sandbox Code Playgroud)

我想知道如何使用Django的ORM实现这一目标.

似乎解决方案是这样的:

MainAccount.objects
    .annotate(total_paid=???)
    .filter(total_paid__gte=2000)
    .order_by('total_paid')
Run Code Online (Sandbox Code Playgroud)

一些说明:

  • MainAccount.extra(...).filter(...).order_by(...)将不起作用.在extra中创建的值无法过滤.

  • 我已经尝试过MainAccount.annotate(total_paid = RawSQL(...)),它工作正常,但在添加.filter()时遇到了一个奇怪的错误.由于某种原因,过滤器调用使用SQL参数改变RawSQL对象,然后"not all arguments converted during string formatting"引发错误.

eln*_*ren 5

正如BogdiG所指出的那样,Django 1.8 Conditional Expressions就是解决方案.

的Python/Django的:

MainAccount.objects.annotate(
    total_paid = Sum(
        Case(
            When(invoices__currency='EUR', then=F('invoices__amount') * 1.12),
            When(invoices__currency='USD', then=F('invoices__amount')),
            When(invoices__currency='GBP', then=F('invoices__amount') * 1.41)
        )
    )
).filter(total_paid__gte=2000).order_by('total_paid')
Run Code Online (Sandbox Code Playgroud)

生成类似于的SQL:

SELECT  main_account.username, 
        SUM(
            CASE 
                WHEN invoice.currency = 'EUR' THEN (invoice.amount * 1.12) 
                WHEN invoice.currency = 'USD' THEN invoice.amount 
                WHEN invoice.currency = 'GBP' THEN (invoice.amount * 1.41) 
                ELSE NULL 
            END
        ) AS total_paid 
FROM main_account 
    INNER JOIN invoice ON ( main_account.username = invoice.username ) 
GROUP BY main_account.username 
HAVING total_paid >= '2000' 
ORDER BY total_paid;
Run Code Online (Sandbox Code Playgroud)