使用子查询注释计数

时间:2018-08-26 15:26:13

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

请帮助我,我在这个问题上坚持了太长时间了:(

我想做什么:

我有以下两种模型:

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

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

我关注了这些链接

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

如果您有任何疑问可以告诉我们,请告诉我。

1 个答案:

答案 0 :(得分:1)

计算所有 Doctor每个 Specialization

我认为您使事情变得过于复杂,可能是因为您认为Count('doctor')将按每个专业(每个医生的专业)计入每个医生。否,如果您Count这样的相关对象,则Django会隐式地查找相关对象。实际上,您根本无法Count('unrelated_model'),只能通过诸如ForeignKeyManyToManyField等的关系(包括反向关系)来查询它们,因为否则它们不是很感性

  

我想在查询集中用具有此专长的医生人数来注释所有专长。

您可以通过以下简单的方法完成此操作:

#  Counting all doctors per specialization (so not all doctors in general)

from django.db.models import Count

Specialization.objects.annotate(
    num_doctors=Count('doctor')
)

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

您还可以过滤同一查询中的Specialization(例如,仅获得以'my'结尾的专业化名称)。只要您不对相关的doctor集进行过滤,Count就可以正常工作(请参见下面的操作方法)。

但是,如果您根据相关的doctor进行过滤,则相关计数将过滤掉这些医生。此外,如果您对另一个相关对象进行过滤,则会产生一个额外的JOIN,它将作为Count乘数 。在这种情况下,最好改用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')
)