Django 布尔注释返回重复项

问题描述 投票:0回答:2

我有以下型号:

class Institution(models.Model):
    pass

class Headquarter(models.Model):
    institution = models.ForeignKey(Institution, related_name='headquarters')

class Audit(models.Model):
    headquarter = models.ForeignKey(Headquarter, related_name='audits')

以及以下查询(基本上,如果一个机构至少进行过一次审核,则 has_visits 必须为 true:

Institution.objects.annotate(
    has_visits=models.Case(
        models.When(headquarters__audits=None, then=False),
        default=True,
        output_field=models.BooleanField()
    )
 )

问题是,如果一个机构有 2 次审计,那么查询集会返回重复的行。我想这与 SQL 级别的连接有关,但我不知道如何纠正它。我找到了这个answer,但我不认为 OuterRef 是我在我的情况下正在寻找的。实现这一目标的正确方法是什么?

python django postgresql annotations django-annotate
2个回答
3
投票

您可以使用

Exists
子查询 [Django-doc]:

from django.db.models import Exists, OuterRef

Institution.objects.annotate(
    has_visits=Exists(
        Audit.objects.filter(headquarter__institution=OuterRef('pk'))
    )
)

其他可能有帮助的方法是让重复项“折叠”,例如使用

Max
:

from django.db.models import Max, Value
from django.db.models.functions import Coalesce

Institution.objects.annotate(
    has_visits=Coalesce(Max(models.Case(
        models.When(headquarters__audits=None, then=False),
        default=True,
        output_field=models.BooleanField()
    )), Value(False))
)

但这可能会降低其可读性和效率。


0
投票

对于一些额外的上下文:我认为这与已知的和记录的限制有关(尽管文档很容易被忽略)。 关于聚合的文档有这一部分(从 Django 4.2 文档复制):

组合多个聚合

使用 annotate() 组合多个聚合将产生错误的结果,因为使用连接而不是子查询:

>>> book = Book.objects.first()
>>> book.authors.count()
2
>>> book.store_set.count()
3
>>> q = Book.objects.annotate(Count('authors'), Count('store'))
>>> q[0].authors__count
6
>>> q[0].store__count
6

对于大多数聚合,无法避免此问题,但是,Count 聚合有一个可能有帮助的独特参数:

>>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store', distinct=True))
>>> q[0].authors__count
2
>>> q[0].store__count
3

本文档是关于在注释中生成连接的聚合,但我怀疑自己生成连接的注释本质上也会发生相同的情况,就像OP的示例一样。

另一种规避限制的方法(尽管它可能不适用于OP的问题)是将聚合放在子查询中。我之前解释过如何使用 django-sql-utils 包来实现此目的:

对于遇到这种情况的其他人来说,一个合理的解决方法似乎是使用子查询来聚合注释。目前,这在 Django 中有点冗长/hacky,正如来自Antoine's comment的 stackoverflow 链接中的多种方法所示。不过,我刚才已经成功使用了 django-sql-utils 包。这听起来有点庞大,但它只有两个实用程序,其中之一是 SubqueryAggregate 类(具有派生的 SubqueryCount、SubquerySum 等),它使将常规连接聚合转换为子查询聚合变得简单而简洁,而无需过多更改结构.

例如,采用 comment 66 中的示例,并使用 django-sql-utils 将第二个注释转换为子查询,您将得到:

Branch.objects.annotate(
    total=Sum('center__client__loan__amount'),
    repaid=SubquerySum('center__client__loan__payment_schedule__payments__principal'),
)

还有一个错误报告跟踪此限制。 OP 的代码有点像 this bug,它作为第一个错误的重复项被关闭。

© www.soinside.com 2019 - 2024. All rights reserved.