Условный агрегат и отличия
Использование 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 сможет дать ответ, который объяснит почему это так.