【问题标题】:Django - Annotate multiple fields from a SubqueryDjango - 从子查询中注释多个字段
【发布时间】:2020-07-09 15:06:07
【问题描述】:

我正在开发一个 Django 项目,在该项目上我有一个“A”对象 (A.objects.all()) 的查询集,并且我需要从“B”对象的子查询中注释多个字段。问题是 annotate 方法只能处理每个参数的一种字段类型(DecimalField、CharField 等),因此,为了注释多个字段,我必须使用类似的东西:

A.objects.all().annotate(b_id          =Subquery(B_queryset.values('id')[:1],
                         b_name        =Subquery(B_queryset.values('name')[:1],
                         b_other_field =Subquery(B_queryset.values('other_field')[:1],
                         ... )

这是非常低效的,因为它为我要注释的每个字段在最终 SQL 上创建一个新的子查询/子选择。我想在它的 values() 参数上使用具有多个字段的相同 Subselect,并在 A 的查询集上对它们全部进行注释。我想使用这样的东西:

b_subquery = Subquery(B_queryset.values('id', 'name', 'other_field', ...)[:1])
A.objects.all().annotate(b=b_subquery)

但是当我尝试这样做(并访问第一个元素 A.objects.all().annotate(b=b_subquery)[0])时,它会引发异常:

{FieldError}Expression contains mixed types. You must set output_field.

如果我设置Subquery(B_quer...[:1], output_field=ForeignKey(B, models.DO_NOTHING)),我会得到一个数据库异常:

{ProgrammingError}subquery must return only one column

简而言之,整个问题是我有多个“属于”A的B,所以我需要使用子查询来为A.objects.all()中的每个A选择一个特定的B并将其附加到该A上,使用 OuterRefs 和一些过滤器(我只想要 B 的几个字段),这对我来说是一个微不足道的问题。

提前感谢您的帮助!

【问题讨论】:

  • 查询是否低效取决于数据库引擎在发现公共子表达式并仅对其进行一次评估方面的能力。由于这是大型 SQL 查询中的一种典型模式,因此应该希望它们非常擅长这一点。

标签: python django django-rest-framework django-orm


【解决方案1】:

在这种情况下我的做法是使用prefetch-related

a_qs = A.objects.all().prefetch_related(
    models.Prefetch('b_set',
        # NOTE: no need to filter with OuterRef (it wont work anyway)
        # Django automatically filter and matches B objects to A
        queryset=B_queryset,
        to_attr='b_records'
    )
)

现在a.b_records 将是一个包含a's 相关b 对象的列表。根据您过滤 B_queryset 的方式,此列表可能仅限于 1 个对象。

【讨论】:

  • 抱歉回复晚了。非常感谢。毕竟这才是我真正需要的。
  • 可以在同一个型号中使用吗?
  • @popcorn prefetch_related 工作基于 FK/M2M 字段,所以如果你有一个自引用外键,是的,你应该能够在同一个模型中使用它。
  • @NebexElias 您无法设置b_set 项目限制,您只能向b_queryset 添加更多过滤,这将导致更少的b_set 项目。检查this example 以获得更高级的过滤。
  • 在不同的注释上,这个答案是正确的,因为它解决了 OP 的问题,但它没有为子查询提供解决方案(这让我来到了这里)。对于那些在非相关模型上使用子查询的人,预取将无济于事……
猜你喜欢
  • 2020-11-26
  • 1970-01-01
  • 2016-03-16
  • 2014-12-22
  • 2020-01-18
  • 1970-01-01
  • 2019-07-18
  • 2015-09-12
  • 2015-09-19
相关资源
最近更新 更多