我有Order
个对象和OrderOperation
个对象,它们表示对订单的操作(创建,修改,取消)。
从概念上讲,订单具有1到许多订单操作。每次对订单执行一次操作时,都会在此操作中计算总数。这意味着当我需要查找订单总数时,我只会得到最后的订单操作总数。
class OrderOperation(models.Model):
order = models.ForeignKey(Order)
total = DecimalField(max_digits=9, decimal_places=2)
class Order(models.Model):
@property
def last_operation(self) -> Optional['OrderOperation']:
try:
qs = self.orderoperation_set.all()
return qs[len(qs) - 1]
except AssertionError: # when there is a negative indexing (no operation)
# IndexError can not happen
return None
@property
def total(self) -> Optional[Decimal]:
last_operation = self.last_operation
return last_operation.total if last_operation else None
由于我收到很多订单,因此每次我要进行简单的过滤(例如“总价低于5欧元的订单”)时,都需要很长时间,因为我需要使用以下内容浏览所有订单,显然是错误的查询:
all_objects = Order.objects.all()
Order.objects.prefetch_related('orderoperation_set').filter(
pk__in=[o.pk for o in all_objects if o.total <= some_value])
数据非规范化?
我可以简单地在total
上创建一个Order
属性,并将每次操作创建时将操作总数复制到订单总数中。
然后,Order.objects.filter(total__lte=some_value)
将起作用。
但是,在复制数据库中的数据之前,我想确保没有更简单/更干净的解决方案。
使用anateate()方法?
我希望能够做到:Order.objects.annotate(total=something_magical_here).filter(total__lte=some_value)
。似乎不可能。
先过滤然后匹配?
order_operations = OrderOperation.objects.filter(total__lte=some_value)
orders = Order.objects.filter(orderoperation__in=order_operations)
这非常快,但是由于我没有过滤最后的操作,但是所有的操作都在这里,因此过滤效果很差。这是错误的。
还有其他想法吗?谢谢。
答案 0 :(得分:1)
似乎不可能。
当然,这是可能的;)您可以使用子查询或一些巧妙的条件表达式。假设您想从上次订单操作中获得总金额,下面是带有子查询的示例:
from django.db.models import Subquery, OuterRef
orders = Order.objects.annotate(
total=Subquery( # [1]
OrderOperation.objects \
.filter(order_id=OuterRef("pk")) \ # [2]
.order_by('-id') \ # [3]
.values('total') \ # [4]
[:1] # [5]
)
)
上面的代码说明:
total
的字段将由子查询填充。您可以在此查询集中将其作为模型Order
的任何其他字段进行访问(在评估之后,在模型实例中或在过滤和其他注释中)。您可以从Django docs了解注释的工作原理。OuterRef
只会替换为对结果SQL查询中所选字段的引用。id
降序排列,因为我们确实想要最新的。如果您要在操作中有其他字段要排序(例如创建日期),请在此处填写。total
值LIMIT
子句添加到SQL查询中,而无需调用它,这就是我们想要的。现在您可以使用:
orders.filter(total__lte=some_value)
仅获取所需的订单。您也可以使用该注释来