Как отфильтровать набор запросов по десятичной дроби, хранящейся в 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
на любое значение по умолчанию, которое вы хотели бы использовать для нечисловых случаев. Веселитесь!