Использование подзапроса для аннотирования графа - PullRequest
0 голосов
/ 26 августа 2018

Пожалуйста, помогите мне, я слишком долго застрял на этом :(

Что я хочу сделать:

У меня есть две модели:

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 голосов
/ 26 августа 2018

Подсчет все Doctor с за Specialization

Я думаю, что вы усложняете ситуацию, возможно потому, что вы думаете, что Count('doctor') будет подсчитывать каждого врача за специализацию (независимо от специализации этого врача). Если у вас Count такой связанный объект, нет, Django неявно ищет связанных объектов. На самом деле вы не можете Count('unrelated_model') вообще, только через отношения (включая обратные), такие как ForeignKey, ManyToManyField и т. Д., Вы можете запросить их, поскольку в противном случае они не очень чувственные .

Я хотел бы аннотировать все специализации в наборе запросов с количеством врачей, которые имеют эту специализацию.

Вы можете сделать это с помощью простого:

#  Counting all doctors <i>per</i> specialization (so not <i>all</i> doctors in general)

from django.db.models import <b>Count</b>

Specialization.objects.annotate(
    <b>num_doctors=Count('doctor')</b>
)

Теперь каждый Specialization объект в этом наборе запросов будет иметь дополнительный атрибут num_doctors, представляющий собой целое число (количество врачей этой специализации).

Вы также можете фильтровать по Specialization s в том же запросе (например, получить только специализации, оканчивающиеся на 'my'). Пока вы не фильтруете соответствующий набор doctor, Count будет работать (см. Раздел ниже, как это сделать).

Если, однако, вы отфильтруете соответствующие doctor s, то соответствующие подсчеты отфильтруют этих врачей. Кроме того, если вы отфильтруете другой связанный объект, то это приведет к дополнительному JOIN, который будет действовать как множитель для Count s. В этом случае может быть лучше использовать num_doctors=Count('doctor', distinct=True). Вы всегда можете использовать distinct=True (независимо от того, делаете ли вы дополнительные JOIN с или нет), но это окажет небольшое влияние на производительность.

Сказанное выше работает, потому что Count('doctor') не просто добавляет всех докторов к запросу, оно делает LEFT OUTER JOIN в таблице doctor s и таким образом проверяет, что specialization_id этого Doctor это именно то, что мы ищем. Таким образом, запрос Django будет выглядеть так:

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

То же самое с подзапросом даст функциональные результаты, но если Django ORM и система управления базами данных не найдут способ оптимизировать это, это может привести к дорогостоящему запросу, поскольку для каждой специализации может привести к дополнительному подзапросу в базе данных.

Подсчет удельный Doctor с за Specialization

Скажите, однако, что вы хотите считать только врачей, чье имя начинается с Джо , тогда вы можете добавить фильтр на соответствующий doctor , как:

#  counting all Doctors with as name Joe per specialization

from django.db.models import Count

Specialization.objects.filter(
    <b>doctor__name__startswith='Joe'</b>  # sample filter
).annotate(
    num_doctors=Count('doctor')
)
...