Django ORM агрегация над связанным полем массива

У меня есть две модели

class Record(Model):
    scorable_entry = models.ForeignKey('Entry',
                                       null=True,
                                       blank=True,
                                       on_delete=models.CASCADE)

class Entry(Model):
    scores = ArrayField(models.IntegerField(), null=True)

и мне нужно отсортировать Records на основе суммы scores на связанной Entry модели.

К сожалению, этот наивный код выбрасывает ошибку

records
.annotate(score_rows=Func(F('scorable_entry__scores'), function='unnest'))
.annotate(scores_sum=sum('score_rows'))
.order_by('-scores_sum')
django.db.utils.NotSupportedError: aggregate function calls cannot contain set-returning function calls

Я использую unnest для преобразования массива в строки (потому что иначе sum не будет работать). Пропуск unnesting не работает, так как sum не работает с массивами

django.db.utils.ProgrammingError: function sum(integer[]) does not exist
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

Каким образом правильно упорядочить элементы по сумме связанного массива с помощью ORM?

Django 3.1, Postgres 12.9

Вы можете создать функцию postgres для суммирования int[] и использовать ее в аннотации

create or replace function int_array_sum(arr int[])
returns int
language plpgsql
as
$$
declare
    result integer;
begin
   select sum(a) into result
   from unnest(arr) a;

    return result;
end;
$$;

Здесь запрос

Record.objects
    .annotate(scores_sum=Func(F('scorable_entry__scores'), function='int_array_sum'))
    .order_by('-scores_sum')

В итоге я использовал миграцию, включая пользовательскую функцию из ответа Eugenij

ARRAY_SUM_FUNCTION = """
create or replace function int_array_sum(arr integer[])
returns int
language plpgsql
as
$$
declare
    result integer;
begin
    select sum(a) into result
    from unnest(arr) a;
    return result;
end;
$$;
"""

Обратите внимание на arr integer[] тип параметра.

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('records', '0003_auto_20151206_1219'),
    ]

    operations = [
        migrations.RunSQL(ARRAY_SUM_FUNCTION)
    ]
Вернуться на верх