djangopostgresqlaggregatedistinct

Conditional aggregate and distinct


Using StringAgg with the distinct=True argument works under normal circumstances, e.g.:

entities = entities.annotate(roles=StringAgg(
    "credits__role__name",
    delimiter=", ",
    distinct=True,
    ordering="credits__role__name"
))

But when used with a conditional expression, it throws the exception django.db.utils.ProgrammingError: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list, e.g.:

releases = releases.annotate(credits_primary=StringAgg(
    Case(When(credits__type="primary", then="credits__entity__name")),
    delimiter=", ",
    distinct=True,
    ordering="credits__entity__name"
))

Why is this, and is there a way to make the second example work?

Edit: here is the full code sample that I'm using currently and works. What I want to do is eliminate duplicate entitys from the aggregated list string.

    if request.GET.get("order[0][name]"):
        order = request.GET["order[0][name]"]
        if order == "credits_primary":
            releases = releases.annotate(credits_primary=StringAgg(
                Case(When(credits__type="primary", then="credits__entity__name")),
                delimiter=", ",
                ordering="credits__entity__name"
            ))
        elif order == "credits_secondary":
            releases = releases.annotate(credits_secondary=StringAgg(
                Case(When(credits__type="secondary", then="credits__entity__name")),
                delimiter=", ",
                ordering="credits__entity__name"
            ))
    else:
        order = "title"
    
    # Order releases
    if request.GET.get("order[0][dir]") == "desc":
        releases = releases.order_by(F(order).desc(nulls_last=True), "title")
    else:
        releases = releases.order_by(F(order).asc(nulls_last=True), "title")

Note that a release may not have any primary or secondary credits, and one entity may have more than one primary or secondary credit for the same release (with different roles).


Solution

  • Try the following query, it should give the expected results:

    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',  
        ),  
    )
    

    You should use filter inside StringAgg, I didn't find such an example in the documentation for StringAgg, but you can see it here by looking at the signature. On this page you can see the use of filter in the Count aggregation function.

    ...
    from django.db.models import Q
    above_5 = Count("book", filter=Q(book__rating__gt=5))
    ...