【问题标题】:How do I convert this complex SQL into a Django model query?如何将这个复杂的 SQL 转换为 Django 模型查询?
【发布时间】:2018-10-16 20:20:20
【问题描述】:

我正在编写一个 Python/Django 应用程序来进行一些股票分析。

我有两个非常简单的模型,如下所示:

class Stock(models.Model):
    symbol = models.CharField(db_index=True, max_length=5, null=False, editable=False, unique=True)

class StockHistory(models.Model):
    stock = models.ForeignKey(Stock, related_name='StockHistory_stock', editable=False)
    trading_date = models.DateField(db_index=True, null=False, editable=False)
    close = models.DecimalField(max_digits=12, db_index=True, decimal_places=5, null=False, editable=False)

    class Meta:
        unique_together = ('stock', 'trading_date')

这是我填充它们的虚拟数据:

import datetime
a = Stock.objects.create(symbol='A')
b = Stock.objects.create(symbol='B')
c = Stock.objects.create(symbol='C')
d = Stock.objects.create(symbol='D')

StockHistory.objects.create(trading_date=datetime.date(2018,1,1), close=200, stock=a)
StockHistory.objects.create(trading_date=datetime.date(2018,1,2), close=150, stock=a)
StockHistory.objects.create(trading_date=datetime.date(2018,1,3), close=120, stock=a)
StockHistory.objects.create(trading_date=datetime.date(2018,4,28), close=105, stock=a)
StockHistory.objects.create(trading_date=datetime.date(2018,5,3), close=105, stock=a)

StockHistory.objects.create(trading_date=datetime.date(2017,5,2), close=400, stock=b)
StockHistory.objects.create(trading_date=datetime.date(2017,11,11), close=200, stock=b)
StockHistory.objects.create(trading_date=datetime.date(2017,11,12), close=300, stock=b)
StockHistory.objects.create(trading_date=datetime.date(2017,11,13), close=400, stock=b)
StockHistory.objects.create(trading_date=datetime.date(2017,11,14), close=500, stock=b)

StockHistory.objects.create(trading_date=datetime.date(2018,4,28), close=105, stock=c)
StockHistory.objects.create(trading_date=datetime.date(2018,4,29), close=106, stock=c)
StockHistory.objects.create(trading_date=datetime.date(2018,4,30), close=107, stock=c)
StockHistory.objects.create(trading_date=datetime.date(2018,5,1), close=108, stock=c)
StockHistory.objects.create(trading_date=datetime.date(2018,5,2), close=109, stock=c)
StockHistory.objects.create(trading_date=datetime.date(2018,5,3), close=110, stock=c)
StockHistory.objects.create(trading_date=datetime.date(2018,5,4), close=90, stock=c)

我想找出过去一周内创下年度新低的所有股票。

但是为了使这个问题更简单,假设我想找到自'2017-05-04' 以来最低点出现在'2018-04-30' 或之后的所有股票。下面是我为找到它而编写的 SQL。它有效。

但我需要帮助确定要编写什么 Django Query 才能获得与此 SQL 相同的结果。我该怎么做?

mysql> select
    ->     s.symbol,
    ->     sh.trading_date,
    ->     low_table.low
    -> from
    ->     (
    ->         select
    ->             stock_id,
    ->             min(close) as low
    ->         from
    ->             stocks_stockhistory
    ->         where
    ->             trading_date >= '2017-05-04'
    ->         group by
    ->             stock_id
    ->     ) as low_table,
    ->     stocks_stockhistory as sh,
    ->     stocks_stock as s
    -> where
    ->     sh.stock_id = low_table.stock_id
    ->     and sh.stock_id = s.id
    ->     and sh.close = low_table.low
    ->     and sh.trading_date >= '2018-04-30'
    -> order by
    ->     s.symbol asc;
+--------+--------------+-----------+
| symbol | trading_date | low       |
+--------+--------------+-----------+
| A      | 2018-05-03   | 105.00000 |
| C      | 2018-05-04   |  90.00000 |
+--------+--------------+-----------+
2 rows in set (0.02 sec)

【问题讨论】:

  • 虽然您的查询很好,但请注意,我们在 1992 年左右停止使用逗号样式的 JOIN。来吧。加入我们。
  • @BurhanKhalid,这个和号不应该是逗号吗?
  • @BurhanKhalid 不应该 modelsobjects

标签: python mysql django


【解决方案1】:

编辑:我设法使用 Django 子查询改进了解决方案。

我们可以使用 Django 的 aggregates with SubQuery expressions 将查询转换为 Django ORM:

  1. 创建一个子查询以检索每个symbol 的最低close

    from django.db.models import OuterRef, Subquery, Min     
    
    lows = StockHistory.objects.filter(
        stock=OuterRef('stock'), 
        trading_date__gte='2017-05-04'
    ).values('stock__symbol')
    .annotate(low=Min('close'))
    .filter(trading_date__gte='2018-04-30')
    
    • 细分:

      • filter 查询集仅获取 trading_date >= '2017-05-04' 的股票。
      • “GROUP BY”stock__symbol(Djnago 中的 group by 示例:GROUP BY ... MIN/MAXGROUP BY ... COUNT/SUM)。
      • annotate 每个元素的最低 (low) 价格。
      • 再次 filter 查询集以仅获取在 trading_date >= '2018-04-30' 上出现 low 字段的对象。
    • 中间结果:

      虽然我们在这个阶段无法得到结果,但子查询将如下所示:

      [
          {'stock__symbol': 'A', 'low': Decimal('105.00000')},            
          {'stock__symbol': 'C', 'low': Decimal('90.00000')}
      ]
      

      我们缺少trading_date

  2. 利用子查询检索特定的StockHistory 对象:

    StockHistory.objects.filter(
        stock__symbol=Subquery(lows.values('stock__symbol')),
        close=Subquery(lows.values('low')),
        trading_date__gte='2018-04-30'
    ).values('stock__symbol', 'trading_date', 'close')
    .order_by('stock__symbol')
    
    • 细分:

      • lows.values('stock__symbol') 和 lows.values('low') 从子查询中检索各自的值。
      • filter 针对lows 子查询值的查询集。还针对指定日期filter,以消除在该日期之前出现的低close 价格。
      • 获取指定的values
      • 将结果按stock__symbol 排序(默认为ascending)。
    • 结果:

      [
          {
              'close': Decimal('105.00000'), 
              'trading_date': datetime.date(2018, 5, 3), 
              'stock__symbol': 'A'
          }, 
          {
              'close': Decimal('90.00000'), 
              'trading_date': datetime.date(2018, 5, 4), 
              'stock__symbol': 'C'
          }
      ]
      

【讨论】:

  • 这个解决方案对我不起作用。我收到了错误NameError: name 'OuterRef' is not defined
  • @SaqibAli 您忘记导入了吗:from django.db.models import OuterRef, Subquery, Min?如果不是这样,您使用的是哪个版本的 Django 和 Python?
  • 糟糕!是的。对不起。导入后,我现在收到此错误:ValueError: This queryset contains a reference to an outer query and may only be used in a subquery.:gist.github.com/saqib-zmi/907398cc695660dbbf9c47e6929ec3db
  • 是的@JohnMoutafis,你是对的。现在,您提供的 Django 给出的结果与我的 SQL 相同。我有一个后续问题。但我会在此期间接受您的回答并单独发布。
  • 这是我的后续问题:stackoverflow.com/questions/50286057/…
【解决方案2】:

对于较新版本的 Django(1.11、2.0):

from django.db.models import Min
low_stocks_qs = StockHistory.objects.filter(trading_date__gt='2017-05-04').annotate(low=Min('close')).filter(trading_date__gte='2018-04-30').order_by('stock__symbol')

您可以遍历查询集以获取 low 和 stock.symbol 的各个值,可能是这样的:

low_stocks_dict = {}
for inst in low_stocks_qs:
    low_stocks_dict[inst.stock.Symbol] = inst.low

【讨论】:

  • 我看到您在此处注释了一个名为 low 的新属性。但你从不使用它。对吗?
  • @SaqibAli 我编辑了我的答案。这就是我介绍low的原因。
  • 我希望 DB 尽可能多地完成工作,而不是我的 python 代码。
  • @SaqibAli 那么你可能更喜欢使用raw SQL,但要小心SQL注入。
猜你喜欢
  • 1970-01-01
  • 2020-04-04
  • 2019-10-27
  • 2017-05-04
  • 2020-12-29
  • 2020-11-14
  • 1970-01-01
  • 1970-01-01
  • 2019-02-15
相关资源
最近更新 更多