【问题标题】:Using Subquery to annotate a Count使用子查询注释计数
【发布时间】:2019-02-01 07:19:15
【问题描述】:

请帮帮我,我在这个问题上卡了太久了:(

我想做什么:

我有这两个模型:

class Specialization(models.Model):
    name = models.CharField("name", max_length=64)
class Doctor(models.Model):
    name = models.CharField("name", max_length=128)
    # ...
    specialization = models.ForeignKey(Specialization)

我想用具有该专业的医生数量来注释查询集中的所有专业。

到目前为止我的解决方案:

我经历了一个循环,我做了一个简单的:Doctor.objects.filter(specialization=spec).count(),但事实证明这太慢且效率低下。 我读的越多,我就越意识到在这里使用SubQuery 来过滤OuterRef 专业的医生是有意义的。这是我想出的:

doctors = Doctor.objects.all().filter(specialization=OuterRef("id")) \
    .values("specialization_id") \
    .order_by()
add_doctors_count = doctors.annotate(cnt=Count("specialization_id")).values("cnt")[:1]

spec_qs_with_counts = Specialization.objects.all().annotate(
    num_applicable_doctors=Subquery(add_doctors_count, output_field=IntegerField())
)

每个专业我得到的输出只有 1。代码只是用 specialization_id 注释每个医生对象,然后注释该组中的计数,这意味着它将是 1。

不幸的是,这对我来说并不完全有意义。在我最初的尝试中,我使用了一个聚合来计数,虽然它本身可以工作,但它不能作为 SubQuery 工作,我收到了这个错误:

This queryset contains a reference to an outer query and may only be used in a subquery.

我之前发布过这个问题,有人建议这样做Specialization.objects.annotate(count=Count("doctor"))

但这不起作用,因为我需要计算特定的医生查询集。

我已经关注了这些链接

但是,我没有得到相同的结果:

如果您有任何问题可以更清楚地说明这一点,请告诉我。

【问题讨论】:

  • 我认为你误解了Count('doctor') 的语义:它计算了相关 doctors,所以这意味着每个专业,你会得到一个(可能)不同的数字。它计算所有名医生,并用该数字注释所有专业。

标签: python django django-aggregation django-annotate django-subquery


【解决方案1】:

问题

问题是 Django 一看到就使用聚合函数添加GROUP BY

解决方案

所以你可以创建你自己的聚合函数,但是让 Django 认为它不是聚合的。就像这样:

doctors = Doctor.objects.filter(
    specialization=OuterRef("id")
).order_by().annotate(
    count=Func('id', 'Count')
).values('count')

spec_qs_with_counts = Specialization.objects.annotate(
    num_applicable_doctors=Subquery(doctors)
)

您可以在此答案中看到有关此方法的更多详细信息:https://stackoverflow.com/a/69020732/10567223

【讨论】:

    【解决方案2】:

    计数所有 Doctors Specialization

    我认为你把事情弄得太复杂了,可能是因为你认为Count('doctor') 会计算每个专业的每个医生(不管那个医生的专业)。它不会,如果你 Count 这样的相关对象,Django 会隐式查找 related 对象。事实上你根本不能Count('unrelated_model'),只有通过ForeignKeyManyToManyField等关系(包括反向)你可以查询这些,因为否则这些不是很敏感 em>。

    我想用具有该专业的医生数量来注释查询集中的所有专业。

    你可以用一个简单的方法做到这一点:

    #  Counting all doctors per specialization (so not all doctors in general)
    
    from django.db.models import Count
    
    Specialization.objects.annotate(
        num_doctors=Count('doctor')
    )

    现在this 查询集中的每个Specialization 对象都将有一个额外的属性num_doctors,它是一个整数(具有该专业的医生的数量)。

    您还可以在同一查询中过滤Specializations(例如,仅获取以'my' 结尾的特化)。只要您不对相关的doctors 集进行过滤,Count 就会起作用(请参阅下文如何执行此操作)。

    如果您过滤相关的doctors,则相关计数将过滤掉这些医生。此外,如果您过滤 另一个 相关对象,那么这将产生一个额外的JOIN,它将充当Counts 的乘数。在这种情况下,最好改用num_doctors=Count('doctor', distinct=True)。您始终可以使用distinct=True(不管您是否使用了额外的JOINs),但它对性能的影响很小。

    上述工作是因为Count('doctor') 不只是将所有 医生添加到查询中,它在doctors 表上创建了一个LEFT OUTER JOIN,从而检查specialization_idDoctor 正是我们正在寻找的那个。因此 Django 将构造的查询如下所示:

    SELECT specialization.*
           COUNT(doctor.id) AS num_doctors
    FROM specialization
    LEFT OUTER JOIN doctor ON doctor.specialization_id = specialization.id
    GROUP BY specialization.id

    对子查询做同样的事情在功能上会得到相同的结果,但是如果 Django ORM 和数据库管理系统没有找到优化它的方法,这可能会导致昂贵的查询,因为对于每个专业化,它然后可能会导致数据库中出现额外的子查询。

    计数特定 Doctors per Specialization

    假设您想统计名以Joe开头的医生,然后您可以添加过滤器 doctor,点赞:

    #  counting all Doctors with as name Joe per specialization
    
    from django.db.models import Count
    
    Specialization.objects.filter(
        doctor__name__startswith='Joe'  # sample filter
    ).annotate(
        num_doctors=Count('doctor')
    )

    【讨论】:

    • 感谢您的回复,非常感谢。我刚试过这个,最初认为这不起作用,但它确实起作用了!你说得对,我把这件事复杂化了,我只是认为医生的计数会计算该专业的所有医生,但过滤器实际上将它们排除在计数之外。感谢您的帮助。
    • annotate + Count 的问题是(正如我发现的那样),Django 生成一个左外连接,然后选择一个 COUNT(DISTINCT).. 这可能非常慢,因为有这么多结果集中生成中间行
    • @little_birdie:是的,但是每个元素执行一个的替代方案会更慢,因为这样会产生每个子查询的往返行程。注释的想法是,如果您需要 all Specializations 的计数,则执行此操作,在这种情况下,这比在每个 Specialization 中计数一个更有趣,因为这是著名的N+1问题。
    • 我不建议多次往返。我建议当你在反向连接上注释(Count()) 时 django 生成的查询可能非常慢,尤其是当你进行多次计数时。因为它对每个表进行外部连接,然后进行 COUNT (DISTINCT).. 这会导致服务器要排序的行数很大。在我的应用程序中,postgres 执行 django 的查询需要 5 秒,而我的手动编码查询((SELECT COUNT(*) .. ) AS acount)在 34 毫秒内执行。
    • @little_birdie: 但是这里我们没有COUNT(DISTINCT ...) 注意ORM查询中没有Count(..., distinct=True),你可以通过print(str(my_django_query.query))获取查询,我已经在两者上都进行了测试一个 MySQL 和 PostgreSQL,两者的结果都符合COUNT(doctor_id) 中的预期(如答案中所列)。特别是如果涉及多个聚合,JOIN 最终将胜过子查询,因为它执行多次扫描(来源:itprotoday.com/sql-server/…
    猜你喜欢
    • 1970-01-01
    • 2022-01-19
    • 2019-06-06
    • 1970-01-01
    • 1970-01-01
    • 2021-09-17
    • 2020-04-15
    • 2018-08-26
    • 1970-01-01
    相关资源
    最近更新 更多