Dav*_* D. 4 python django django-queryset
我有Order对象和OrderOperation对象代表对订单的操作(创建、修改、取消)。
从概念上讲,一个订单有一对多的订单操作。每次对订单进行操作时,都会在此操作中计算总数。这意味着当我需要查找订单的属性时,我只是使用子查询获取最后一个订单操作属性。
class OrderOperation(models.Model):
order = models.ForeignKey(Order)
total = DecimalField(max_digits=9, decimal_places=2)
class Order(models.Model)
# ...
class OrderQuerySet(query.Queryset):
@staticmethod
def _last_oo(field):
return Subquery(OrderOperation.objects
.filter(order_id=OuterRef("pk"))
.order_by('-id')
.values(field)
[:1])
def annotated_total(self):
return self.annotate(oo_total=self._last_oo('total'))
Run Code Online (Sandbox Code Playgroud)
这样,我就可以跑了my_order_total = Order.objects.annotated_total()[0].oo_total。它工作得很好。
计算总数很容易,因为它是一个简单的值。但是,当存在 M2M 或 OneToMany 字段时,此方法不起作用。例如,使用上面的示例,让我们添加此字段:
class OrderOperation(models.Model):
order = models.ForeignKey(Order)
total = DecimalField(max_digits=9, decimal_places=2)
ordered_articles = models.ManyToManyField(Article,through='orders.OrderedArticle')
Run Code Online (Sandbox Code Playgroud)
编写如下内容不起作用,因为它只返回 1 个外键(不是所有 FK 的列表):
def annotated_ordered_articles(self):
return self.annotate(oo_ordered_articles=self._last_oo('ordered_articles'))
Run Code Online (Sandbox Code Playgroud)
整个目的是允许用户在所有订单中进行搜索,在输入中提供列表或文章。例如:“请查找至少包含第 42 条或第 43 条的所有订单”,或“请查找完全包含第 42 条和第 43 条的所有订单”等。
如果我能得到类似的东西:
>>> Order.objects.annotated_ordered_articles()[0].oo_ordered_articles
<ArticleQuerySet [<Article: Article42>, <Article: Article43>]>
Run Code Online (Sandbox Code Playgroud)
甚至:
>>> Order.objects.annotated_ordered_articles()[0].oo_ordered_articles
[42,43]
Run Code Online (Sandbox Code Playgroud)
那将解决我的问题。
ArrayAgg(我正在使用 pgSQL)之类的东西可以解决问题,但我不确定在我的情况下如何使用它。values()似乎不打算处理文档中所述的 M2M 和 1TM 关系的方法有关:values() 和 values_list() 都旨在针对特定用例进行优化:检索数据子集而无需创建模型实例的开销。当处理多对多和其他多值关系(例如反向外键的一对多关系)时,这个比喻就失效了,因为“一行一个对象”假设不成立。
ArrayAgg如果您只想从所有文章中获取一个变量(即名称),那将会很棒。如果您需要更多,有一个更好的选择:
prefetch_related相反,您可以将每个Order, latestOrderOperation作为整个对象进行预取。这增加了OrderOperation无需额外魔法即可轻松获取任何领域的能力。
唯一需要注意的是,当所选订单没有操作时,您将始终获得一个包含一个操作的列表或一个空列表。
为此,您应该将查询集prefetch_related模型与Prefetch对象和自定义查询结合使用OrderOperation。例子:
from django.db.models import Max, F, Prefetch
last_order_operation_qs = OrderOperation.objects.annotate(
lop_pk=Max('order__orderoperation__pk')
).filter(pk=F('lop_pk'))
orders = Order.objects.prefetch_related(
Prefetch('orderoperation_set', queryset=last_order_operation_qs, to_attr='last_operation')
)
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用order.last_operation[0].ordered_articles获取特定订单的所有订购文章。您可以添加prefetch_related('ordered_articles')到第一个查询集以提高性能并减少对数据库的查询。
令我惊讶的是,你的想法ArrayAgg是正确的。我不知道有一种方法可以用数组进行注释(而且我相信除了 Postgres 之外还没有其他后端)。
from django.contrib.postgres.aggregates.general import ArrayAgg
qs = Order.objects.annotate(oo_articles=ArrayAgg(
'order_operation__ordered_articles__id',
'DISTINCT'))
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用ArrayField 查找过滤结果查询集:
# Articles that contain the specified array
qs.filter(oo_articles__contains=[42,43])
# Articles that are identical to the specified array
qs.filter(oo_articles=[42,43,44])
# Articles that are contained in the specified array
qs.filter(oo_articles__contained_by=[41,42,43,44,45])
# Articles that have at least one element in common
# with the specified array
qs.filter(oo_articles__overlap=[41,42])
Run Code Online (Sandbox Code Playgroud)
'DISTINCT' 仅当操作可能包含重复的文章时才需要。
您可能需要调整传递给ArrayAgg函数的字段的确切名称。为了后续过滤工作,您可能还需要将 id 字段转换为ArrayAggto int,否则 Django 会将 id 数组转换为::serial[],并且我的 Postgres 抱怨type "serial[]" does not exist:
from django.db.models import IntegerField
from django.contrib.postgres.fields.array import ArrayField
from django.db.models.functions import Cast
ArrayAgg(Cast('order_operation__ordered_articles__id', IntegerField()))
# OR
Cast(ArrayAgg('order_operation__ordered_articles__id'), ArrayField(IntegerField()))
Run Code Online (Sandbox Code Playgroud)
更仔细地查看您发布的代码,您还必须筛选出OrderOperation您感兴趣的代码;上面的查询查看相关订单的所有操作。
| 归档时间: |
|
| 查看次数: |
3022 次 |
| 最近记录: |