【问题标题】:SQL code conversion into a single Django ORM query?SQL 代码转换成单个 Django ORM 查询?
【发布时间】:2019-12-12 23:43:21
【问题描述】:

我正在尝试将以下 SQL 查询转换为单个 Django 查询,以尽量减少执行的 sql 查询的数量,但是除了使用 for 循环之外我无法做到这一点。

如果有人能给我提供预期的 Django 查询,我将不胜感激

SQL 查询:

select FUND_ID, REPORT_DATE, PURCHASE_NAV_PER_SHARE
FROM FUNDS_HISTORY
WHERE REPORT_DATE = (
   SELECT Max(REPORT_DATE)
   FROM FUNDS_HISTORY fund_history
   where fund_history.FUND_ID = FUNDS_HISTORY.FUND_ID
)

型号:

class Fund(models.Model):
    name_en = models.CharField(max_length=256, blank=True, null=True)


class History(models.Model):
    fund = models.ForeignKey('funds.Fund', on_delete=models.CASCADE, blank=True, null=True)
    purchase_nav_per_share = models.BigIntegerField(blank=True, null=True)
    report_date = models.DateField(blank=True, null=True)

【问题讨论】:

  • 请分享相关模型。
  • edit您的问题。

标签: sql django python-3.x django-models


【解决方案1】:

我们可以用最大的相关Fund来注释History对象,比如:

History.objects.annotate(
    max_date=Max('fund__history__report_date')
).filter(
    report_date=F('max_date')
)

或者更简单一点:

History.objects.filter(
    report_date=Max('fund__history__report_date')
)

您可以通过以下方式稍微提高性能:

from django.db.models import Max, OuterRef, Subquery

History.objects.filter(
    report_date=Subquery(
        History.objects.filter(
            fund_id=OuterRef('fund_id')
        ).values('fund_id').annotate(
            last_date=Max('report_date')
        ).values('last_date')[:1]
    )
)

这将导致如下查询:

SELECT history.id, history.fund_id, history.purchase_nav_per_share, history.report_date
FROM history
WHERE history.report_date = (
    SELECT MAX(U0.report_date) AS last_date
    FROM history U0
    WHERE U0.fund_id = history.fund_id
    GROUP BY U0.fund_id
    LIMIT 1
)

上面和问题中的查询很相似。

我们在这里获得History 对象,这通常比掌握值本身要好,因为这是一种“原始痴迷”反模式。

【讨论】:

  • 其实我希望 purchase_nav_per_share 的值与每个fund_id 的最大报告日期相关。
  • @Erfan:但是你明白了:这个查询集中的History 对象当然有一个.purchase_nav_per_share 属性。
  • 您的建议的翻译是一个复杂的 SQL 查询,由于执行时间长,我无法获得输出
  • @Erfan:它有两个 JOIN:一个在 fund 模型上,它始终是一个,然后是另一个历史,这将再次产生一些额外的值。但一般来说,问题和答案中的查询都应该在 O(n^2) 中运行,其中 n 的数量为 Historys。
  • 我不确定,但 Django 可能会以不正确的方式翻译它,即以下 sql 代码。我正在使用django debug toolbars 来检索 sql 查询
【解决方案2】:

您可以将上面的 sql 查询翻译为 django ORM。

History.objects.annotate(report_date_max=Max('report_date')).filter(report_date=F('report_date_max')).values_list('fund_id', 'report_date', 'purchase_nav_per_share')

模型字段名称可能不正确,因为您没有共享相关字段名称。

【讨论】:

  • 感谢您的回复。您的查询似乎与以下 sql 相同,但我不希望查询返回每个基金 id 的一条记录,而您的建议没有提供我想要的输出。 SELECT "funds_history"."fund_id", "funds_history"."report_date", "funds_history"."purchase_nav_per_share" FROM "funds_history" GROUP BY "funds_history"."id" ORDER BY "funds_history"."id" ASC
  • 试试这个History.objects.values_list('fund_id').annotate(report_date_max=Max('report_date')).filter(report_date=F('report_date_max')).values_list('fund_id', 'report_date', 'purchase_nav_per_share')
猜你喜欢
  • 2017-09-10
  • 2021-07-06
  • 2016-04-20
  • 2020-09-12
  • 1970-01-01
  • 2019-10-17
  • 2022-08-18
  • 2018-06-10
相关资源
最近更新 更多