如何使用 Django ORM 将两个由中间模型相关的模型相加

How to cumsum two models related by an intermediate model using Django ORM

问题

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

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 的第二个 table 更正它。用户甚至可以在第一个价格值日期之前更正起始值:

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

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

如何解决这个问题并使结果显示在第三个 table 中? Django ORM 是否能够以正确的方式执行此操作?

最后我这样做了:

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,那么我们可以抛出请求的查询太长的异常。