Django: аннотирование Sum() двух столбцов в разных таблицах

Предполагая, что у меня есть следующие модели: как я могу аннотировать общее количество публикаций и общий охват историй каждого влиятельного лица в моем наборе запросов?

class Influencer(models.Model):
    name = models.CharField(max_length=100)

class Posting(models.Model):
    influencer = models.ForeignKey(Influencer, on_delete=models.CASCADE)
    reach = models.IntegerField()

class Story(models.Model):
    influencer = models.ForeignKey(Influencer, on_delete=models.CASCADE)
    reach = models.IntegerField()

Я пробовал это:

queryset = Influencer.objects.all()
queryset = queryset.annotate(posting_reach=Sum("posting__reach"))
queryset = queryset.annotate(story_reach=Sum("story__reach"))

Однако при таком подходе значения не рассчитываются правильно (я полагаю, из-за LEFT OUTER JOIN, созданного Sum()). Как бы я сделал это в Джанго?

Можете ли вы объяснить нам, почему вы говорите, что это неправильно? Кроме того, пожалуйста, используйте print(queryset.query) и включите это. Это может дать нам больше информации.

schillingt 18.12.2020 17:05

Попробуйте с different=True внутри count или напишите выражения подзапроса docs.djangoproject.com/en/3.1/topics/db/aggregation/…

iklinac 18.12.2020 17:07

@iklinac: само по себе это не сработает. Например, если есть два Posting с одинаковым количеством reach, то вы считаете их только один раз.

Willem Van Onsem 18.12.2020 17:07

@schillingt: из-за двух LEFT OUTER JOIN они будут действовать как множители друг для друга, поскольку каждый связанный Story будет повторяться для каждого связанного Posting и наоборот.

Willem Van Onsem 18.12.2020 17:08

Не уверен, как именно реализовано отличие внутри count, поэтому вы, вероятно, правы, хороший обходной путь без использования подзапроса.

iklinac 18.12.2020 17:08

@iklinac: обычно тот факт, что это подсчет, не имеет значения, он обычно работает как COUNT(DISTINCT posting.reach), поэтому сначала проходит фильтр уникальности, прежде чем передать его подсчету. Сумма не «знает», что она подсчитывает различные значения. Это делается как «предварительная обработка» перед передачей набора значений агрегату COUNT.

Willem Van Onsem 18.12.2020 17:09

Да, вы правы, немного погуглил :)

iklinac 18.12.2020 17:11
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python.
Некоторые методы, о которых вы не знали, что они существуют в Python.
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
7
1 228
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это действительно не сработает, потому что ваш запрос будет состоять из двух LEFT OUTER JOINs:

SELECT influencer.*, SUM(posting.reach), SUM(story.reach)
FROM influencer
LEFT OUTER JOIN posting ON posting.influencer_id = influencer.id
LEFT OUTER JOIN story ON story.influencer_id = influencer.id

два LEFT OUTER JOIN, таким образом, будут действовать как множители друг друга, и, таким образом, сумма posting.reach будет умножена на количество связанных story, и наоборот.

Хитрость может состоять в том, чтобы разделить его на количество элементов другого отношения, поэтому:

from django.db.models import Count, Sum, Value
from django.db.models.functions import Coalesce

queryset = Influencer.objects.annotate(
    posting_reach=Sum('posting__reach') / Coalesce(Count('story'), Value(1)),
    story_reach=Sum('story__reach') / Coalesce(Count('posting'), Value(1))
)

хорошо, это может сработать, но, честно говоря, это выглядит не очень "питоновски"

RaideR 18.12.2020 17:11

@RaideR: альтернативой является Subquery, но это внесет еще больше «шума».

Willem Van Onsem 18.12.2020 17:11

Для всех, кто заинтересован в решении подзапроса:

from django.db.models import (OuterRef, Subquery, Sum, IntegerField)

subs = {
    'posting_reach': Posting.objects.filter(influencer=OuterRef('pk')),
    'story_reach': Story.objects.filter(influencer=OuterRef('pk')),
}
qs = Influencer.objects.annotate(
    **{key: Subquery(
            sub_q.annotate(sum=Sum('reach')).values('sum'),
            output_field=IntegerField()
        ) for key, sub_q in subs
    }
)

Другие вопросы по теме

Django - файл CSS не работает. Получение ошибки 404. "Не удалось загрузить ресурс: сервер ответил со статусом 404 (не найдено)"
Django - как мне использовать ввод из формы выбора для сравнения данных в моей базе данных и вывода их на другую страницу?
Добавление UniqueConstraint в поле ManyToMany
Как удалить файл в python внутри контейнера докеров?
Как исправить проблемы с импортом с помощью внешней базы данных Django? [УтверждениеОшибка:]
Django не может отобразить предоставленный шаблон выхода из системы
Django FormView: добавить, если запись не существует, и обновить, если она существует
Переход от многотабличной модели наследования к абстрактным базовым классам в Django
Как подключить существующую внешнюю базу данных PostgreSQL для автоматического создания файла Models.py для Django Rest Framework?
Джанго REST-фреймворк. Как получить полный URL-адрес страницы с протоколом, доменом и путем?