【问题标题】:How to cumsum two models related by an intermediate model using Django ORM如何使用 Django ORM 对中间模型相关的两个模型进行求和
【发布时间】:2021-07-27 16:30:13
【问题描述】:

问题

假设我有一个名为 Price 的表,它是一个带有时间戳、值和前一天差异的时间序列。为了简化表格,我只放了日期而不是小时、分钟等:

timestamp value difference
2021-01-21 500 500
2021-01-22 1000 500
2021-01-23 1500 500
2021-01-24 2000 500
2021-01-25 2500 500

这些值可能不正确,用户可以随时使用名为 CorrectedPrice 的第二个表进行更正。用户甚至可以在第一个价格值的日期之前更正起始值:

timestamp value
2021-01-15 1000
2021-01-23 500

通过合并这两个信息,日期 2021-01-21 和 2021-01-26 之间生成的查询集应该是:

timestamp value
2021-01-21 1500
2021-01-22 2000
2021-01-23 1000
2021-01-24 1500
2021-01-25 2000

Django 模型

我们有一个Stock模型:

class Stock(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.Charfield(unique=True)

价格模型:

class Price(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    value = models.IntegerField()
    difference = models.IntegerField()  # done with a signal on pre_save
    timestamp = AutoCreatedField()
    stock = models.ForeignKey(Stock, on_delete=models.CASCADE)

然后我们有 CorrectedPrice 模型:

class CorrectedPrice(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    value = models.IntegerField()
    timestamp = AutoCreatedField()
    stock = models.ForeignKey(Stock, on_delete=models.CASCADE)

我尝试了什么

start_date = ... # 2021-01-21
end_date = ... # 2021-01-26

Price.objects.filter(
    name=stock_name, 
    timestamp__range(start_date, end_date)
).annotate(
    value=Window(Sum("difference"), order_by=F("timestamp").asc()),
    timestamp=F("timestamp")
)

这基本上没有考虑到 CorrectedPrice 表。我设法在value=Window(Sum("difference"), order_by=F("timestamp").asc()) 之后添加了一个常量,例如value=Window(Sum("difference"), order_by=F("timestamp").asc()) + 1,但这只会考虑一个值。

如何解决这个问题,并在第三张表中显示结果? Django ORM 是否能够以适当的方式做到这一点?

【问题讨论】:

  • 如果已经存储为列,为什么还要使用差的总和来计算值?实际上,如果您尝试添加与现有字段同名的注解,ORM 将引发您的注解与模型上的字段冲突的异常。
  • 我认为使用差异可能更容易进行查询。如果用户将修正后的价格设置为 100,如果差值为 200,那么我们知道我们必须添加 100+200。而不是在查询本身中回顾前一天价格的价值。您提出什么替代解决方案?
  • 首先,存储值和差异是多余的。一个总是可以从另一个计算出来的。我的偏好是存储该值并在需要时计算差异,但您的应用程序可能会考虑使存储差异更好。其次,我不明白您为什么要使用差异来计算价值。如果用户更正价格,是否应该影响所有后续价格?
  • 是的,它应该会影响所有后续价格,但计算可能会很密集,因此需要工作人员才能完成,直到最新数据。此外,当工作人员已经在运行时,用户可能会更改价格,这会增加额外的复杂性。保留原始股票价格信息对于其他用例至关重要。合并更正价格和原始价格是一项关键功能,但每 x 个月使用一次。大多数用户只想查看 7 天日期范围内的第 3 个表格。

标签: python sql django django-orm


【解决方案1】:

最后我做到了:

class CorrectedPrice(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    value = models.IntegerField()
    timestamp = AutoCreatedField()
    stock = models.ForeignKey(Stock, on_delete=models.CASCADE)
   
    def _get_correct_prices(self):
        queryset = CorrectedPrice.objects.filter(stock=self.stock)

        return (
            queryset.first()
            .stock.price_set.filter(
                self.timestamp,
                Coalesce(
                    Subquery(
                        queryset.filter(created_at__get(self.timestamp)
                        .order_by("timestamp")
                        values("timestamp")[:1]
                    ),
                    timezone.now()
                ),
            )
        )
        .annotate(
            series=Window(Sum("difference"), order_by=F("timestamp").asc()) + self.value
        )
        .values("timestamp", "series")

    correct_prices = property(_get_correct_prices)

每次使用 correct_prices 字段查询 CorrectedPrice 时,它​​都会计算正确的价格时间序列,就好像它是模型的一个字段一样。它是即时计算的,就像我需要它一样。

这解决了我的问题,但有一个小缺点,它没有考虑日期范围,并且可能计算太长的时间序列。为此,我们可以通过在.values("timestamp", "series") 之后添加实例限制来解决查询,如下所示:.values("timestamp", "series")[:1000]

如果元素的数量等于 1000,那么我们可以抛出一个异常,即询问的查询太长。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-12
    相关资源
    最近更新 更多