【问题标题】:Annotating results of complicated subqueries in Django 1.8 to QuerySet将 Django 1.8 中复杂子查询的结果注释到 QuerySet
【发布时间】:2015-10-29 20:15:30
【问题描述】:

我必须遵循数据库架构:

main_account - 用户名 发票 - 数量 - 货币:“欧元”或“美元”或“英镑” - 用户名(main_account 有很多发票)

我的目标是以美元列出“最高支付者”。因此,我必须从发票表中计算每个用户的“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

我想知道如何使用 Django 的 ORM 来完成这项工作。

似乎解决方案是这样的:

MainAccount.objects
    .annotate(total_paid=???)
    .filter(total_paid__gte=2000)
    .order_by('total_paid')

一些注意事项:

  • MainAccount.extra(...).filter(...).order_by(...) 将不起作用。 extra 中创建的值不能被过滤掉。

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

【问题讨论】:

  • 看看今天早些时候回答的这里:stackoverflow.com/questions/31852672/…;尝试使用 Django 1.8 中的新条件表达式;让我知道您是否可以在阅读答案/文档后弄清楚
  • 我会试试的。但是,我已经尝试使用 MySQL 的 CASE 创建上面的 SQL,并且它对于当前版本(不到秒)非常慢(查询约 30 秒)。

标签: mysql sql django django-queryset django-orm


【解决方案1】:

正如 BogdiG 所指出的,Django 1.8 条件表达式是解决方案。

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')

生成类似于以下的 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;

【讨论】:

  • 这个功能太棒了。当然激励我升级到 1.8
猜你喜欢
  • 2015-03-27
  • 1970-01-01
  • 2019-08-03
  • 1970-01-01
  • 2014-12-22
  • 2013-03-19
  • 2021-08-28
  • 1970-01-01
  • 2017-08-29
相关资源
最近更新 更多