Условный агрегат и отличия

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

Вернуться на верх