如何使用 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,那么我们可以抛出请求的查询太长的异常。
问题
假设我有一个名为 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,那么我们可以抛出请求的查询太长的异常。