Как отфильтровать набор запросов по десятичной дроби, хранящейся в JSON?

Я храню строковое представление десятичной дроби в JSONField и хочу фильтровать по нему. Вот модель:

class Asset:
    cached_data = JSONField()

Содержание JSONFields:

{
    "price": "123.456"
}

Я перепробовал множество способов отфильтровать queryset по этому значению. Последний из них был

Asset.objects.annotate('price_as_decimal': Cast('cached_data__price', output_field=DecimalField())).filter(price_as_decimal__gt=100)

но он выдает ошибку:

django.db.utils.DataError: invalid input syntax for type integer: "none"

хотя я уверен, что для этого ключа есть значение в поле

Правильный синтаксис:

Asset.objects.annotate(
    # 'price_as_decimal': Cast('cached_data__price', output_field=DecimalField())
    price_as_decimal=Cast('cached_data__price', output_field=DecimalField())
).filter(price_as_decimal__gt=100)

Кажется, это работает нормально, хотя в документации Django 4.2 говорится, что нужно использовать KT трансформацию:

Asset.objects.annotate(
    # 'price_as_decimal': Cast('cached_data__price', output_field=DecimalField())
    # price_as_decimal=Cast('cached_data__price', output_field=DecimalField())
    price_as_decimal=Cast(KT('cached_data__price'), output_field=DecimalField())
).filter(price_as_decimal__gt=100)

Ссылка: https://docs.djangoproject.com/en/5.0/topics/db/queries/#kt-expressions

Правильный синтаксис:

from django.db.models import DecimalField, Cast

Asset.objects.annotate(
   price_as_decimal=Cast('cached_data__price', output_field=DecimalField())
).filter(price_as_decimal__gt=100)

Ошибка, с которой вы столкнулись, invalid input syntax for type integer: "none", предполагает, что есть проблема с данными, хранящимися в вашем JSONField. Похоже, что вместо допустимого числового значения может быть значение "none" (возможно, "null" или None).

Вот пересмотренная версия вашего набора вопросов, которая позволит решить эту проблему:

from django.db.models import F, ExpressionWrapper, DecimalField
from django.db.models.functions import Cast

Asset.objects.annotate(
    price_as_decimal=ExpressionWrapper(
        Cast(F('cached_data__price'), output_field=DecimalField()),
        output_field=DecimalField(),
    )
).filter(price_as_decimal__gt=100)

Этот набор запросов пытается привести значение, хранящееся в cached_data__price, к DecimalField. Однако если в нем есть значения None или нечисловые строки, например "none", он выдаст ошибку. Чтобы справиться с этим, вам может понадобиться очистить данные или использовать условное выражение, чтобы обрабатывать эти случаи по-другому.

Например, если вы хотите рассматривать None или нечисловые строки как нулевые, вы можете использовать Coalesce:

from django.db.models import F, ExpressionWrapper, DecimalField
from django.db.models.functions import Cast, Coalesce

Asset.objects.annotate(
    price_as_decimal=ExpressionWrapper(
        Cast(Coalesce(F('cached_data__price'), 0), output_field=DecimalField()),
        output_field=DecimalField(),
    )
).filter(price_as_decimal__gt=100)

Это преобразует любые значения None или нечисловые строки к нулю перед попыткой приведения. Вы должны изменить значение по умолчанию в Coalesce на любое значение по умолчанию, которое вы хотели бы использовать для нечисловых случаев. Веселитесь!

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