Условный агрегат и отличия
Использование StringAgg с аргументом distinct=True работает в обычных условиях, например:
entities = entities.annotate(roles=StringAgg(
"credits__role__name",
delimiter=", ",
distinct=True,
ordering="credits__role__name"
))
Но при использовании с условием expression он выбрасывает исключение django.db.utils.ProgrammingError: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list, например:
releases = releases.annotate(credits_primary=StringAgg(
Case(When(credits__type="primary", then="credits__entity__name")),
delimiter=", ",
distinct=True,
ordering="credits__entity__name"
))
Почему так происходит, и есть ли способ заставить второй пример работать?
Ошибка возникает из-за того, что PostgreSQL требует, чтобы столбцы ORDER BY отображались в DISTINCT.
убедитесь, что и выражение ORDER BY, и аргумент DISTINCT выровнены. Вы можете использовать Filter для предварительной фильтрации перед агрегированием, например, так:
from django.db.models import StringAgg, Case, When, Value
from django.db.models.functions import Cast
releases = releases.annotate(
credits_primary=StringAgg(
Cast(Case(When(credits__type="primary", then="credits__entity__name"), default=Value("")), output_field=models.TextField()),
delimiter=", ",
distinct=True,
ordering="credits__entity__name"
)
)
Альтернатива: используйте Filter Вместо Case
Если ваше выражение Case действует как filter, другой подход заключается в использовании фильтра непосредственно в агрегировании:
from django.db.models import Q, F
releases = releases.annotate(
credits_primary=StringAgg(
F("credits__entity__name"),
delimiter=", ",
distinct=True,
ordering="credits__entity__name"
).filter(credits__type="primary")
)
Скажите мне, помогло ли это.
Попробуйте выполнить следующий запрос, он должен дать ожидаемые результаты:
from django.db import models
from django.contrib.postgres.aggregates import StringAgg
releases.annotate(
credits_primary=StringAgg(
'credits__entity__name',
# or `models.Q(credits__type='secondary')` in elif condition
filter=models.Q(credits__type='primary'),
delimiter=', ',
distinct=True,
ordering='credits__entity__name',
),
)
Вы должны использовать filter внутри StringAgg, я не нашел такого примера в документации для StringAgg, но вы можете увидеть его здесь взглянув на подпись. На этой странице вы можете ознакомиться с использованием filter в функции агрегирования Count.
...
from django.db.models import Q
above_5 = Count("book", filter=Q(book__rating__gt=5))
...
Редактировать: смотрите этот ответ для более изящного и удобочитаемого решения.
<время работы/>По причинам, которые я, честно говоря, совершенно не понимаю, он, похоже, сортируется по credits__entity__name, даже если ordering="credits__entity__name" опущен. Возможно, по умолчанию он упорядочен по алфавиту или по полю, значение которого передается в условном expression. В документах Django не указано поведение по умолчанию.
(обратите внимание, что в модели Credit по умолчанию используется порядок , а не по объектам)
Другими словами, это приводит к желаемому результату:
releases = releases.annotate(credits_primary=StringAgg(
Case(When(credits__type="primary", then="credits__entity__name")),
delimiter=", ",
distinct=True,
))
Надеюсь, кто-нибудь с более глубоким пониманием Django и/или PostgreSQL сможет дать ответ, который объяснит почему это так.