Django queryset StringAgg на поле массива

У меня есть некоторые данные, которые включают размеры, очень похожие на модель ниже.

class Product(models.Model):
    width = models.CharField()
    height = models.CharField()
    length = models.CharField()

В аннотации у нас есть поле под названием at_size, которое выдает данные типа:

  • [None, None, None]
  • ['200', '000', '210']
  • ['180', None, None]

Это было сделано следующим образом (благодаря: )https://stackoverflow.com/a/70266320/5731101:

    class Array(Func):
        template = '%(function)s[%(expressions)s]'
        function = 'ARRAY'

    out_format = ArrayField(CharField(max_length=200))

    annotated_qs = Product.objects.all().annotate(
        at_size=Array(F('width'), F('height'), F('length'), 
            output_field=out_format)
    )

Я пытаюсь заставить это преобразоваться в:

  • ''
  • '200 x 000 x 210'
  • '180'

В коде это может выглядеть примерно так ' x '.join([i for i in data if i]). Но поскольку мне нужно выполнить это с помощью функций базы данных, это немного сложнее

Я играл со StringAgg, но я продолжаю получать:

HINT: No function matches the given name and argument types. You might need to add explicit type casts.

Похоже, что для начала мне нужно убедиться, что значения None исключены из начальной функции Array-func. Но я не уверен, с чего начать. Как я могу этого добиться?

Оказалось, что проблема была двоякой.

  1. Очистка от значений Null может быть выполнена с помощью array_remove
  2. .
  3. Склеивание строк с разделителем через StringAgg работает только если входные данные являются строками. Но поскольку мы используем массив, этот способ не подходит. Вместо этого используем array_to_string
  4. .

Конечный результат выглядит следующим образом:

    class Array(Func):
        # https://www.postgresql.org/docs/9.6/functions-array.html
        template = '%(function)s[%(expressions)s]'
        function = 'ARRAY'

    class ArrayRemove(Func):
        # https://www.postgresql.org/docs/9.6/functions-array.html
        function = 'array_remove'

    class ArrayToString(Func):
        # https://stackoverflow.com/a/57873772/5731101
        function = 'array_to_string'

    out_format = ArrayField(CharField(max_length=200))

    annotated_qs = annotated_qs.annotate(
        at_size=ArrayToString(
            ArrayRemove(
                Array(F('width'), F('height'), F('length'), output_field=out_format),
                None,                
            ),
            Value(" x "),
            Value(''),
            output_field=CharField(max_length=200),
        )
    )

в результате получается желаемый формат:

for product in annotated_qs:
    print(product.at_size)

180 x 000 x 200
180 x 026 x 200
180 x 7 x 200
180 x 000 x 200
200 x 000 x 220
180 x 000 x 200
175 x 230 x 033
160 x 000 x 200

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