使用子查询注释计数

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

请帮助我,我已经被这个问题困扰太久了:(

我想做的事:

我有这两个型号:

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"))

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

我已关注这些链接

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

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

python django django-aggregation django-annotate django-subquery
3个回答
30
投票

问题

问题是 Django 一旦发现使用聚合函数就会添加

GROUP BY

解决方案

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

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

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

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

还可以在有关 在子查询表达式中使用聚合func 表达式的文档中找到有用的信息。


11
投票

计数 全部
Doctor
s 每个
Specialization

我认为你让事情变得过于复杂,可能是因为你认为

Count('doctor')
会计算每个专业的每个医生(无论该医生的专业是什么)。事实并非如此,如果您
Count
此类相关对象,Django会隐式查找相关对象。事实上你根本不能
Count('unrelated_model')
,只有通过诸如
ForeignKey
ManyToManyField
等关系(包括反向)才能查询这些,因为否则这些不是很感性

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

您可以通过简单的操作来做到这一点:

#  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
,它是一个整数(具有该专业的医生数量)。

您还可以在同一查询中过滤

Specialization
(例如,仅获取以
'my'
结尾的专业化)。只要您不过滤相关的
doctor
设置,
Count
就会起作用(请参阅下面的部分如何执行此操作)。

但是,如果您过滤相关的

doctor
,那么相关计数将过滤掉这些医生。此外,如果您过滤 another 相关对象,那么这将导致额外的
JOIN
,它将充当 Count
multiplier
。在这种情况下,最好使用
num_doctors=Count('doctor', distinct=True)
代替。您始终可以使用
distinct=True
(无论您是否执行额外的
JOIN
),但它会对性能产生很小的影响。

上面的代码之所以有效,是因为

Count('doctor')
并不简单地将 all 医生添加到查询中,而是在
LEFT OUTER JOIN
s 表上创建
doctor
,从而检查该
specialization_id
Doctor
是否正是我们正在寻找的一位。因此 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 和数据库管理系统没有找到优化此方法的方法,这可能会导致昂贵的查询,因为对于每个专业化,它都可能导致在数据库中的额外子查询中。

计算 特定
Doctor
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')
)

0
投票

这是最有效的方法

specialization_with_doctor_counts = Specialization.objects.annotate(doctor_count=Count('doctor')).order_by('-doctor_count')

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