#django #django-orm
Вопрос:
У меня есть таблица базы данных, которая представляет расходы, связанные с данным продуктом.
Эти расходы, учитывая, что они ежедневные, имеют from_date
(дату, в которую они начались) и to_date
(дату, в которую они закончились). to_date
может быть нулевым, так как эти расходы все еще могут продолжаться.
Учитывая 2 Python datetime
s, start_date
и end_date
мне нужно указать в ORM общую сумму , потраченную за период my_product
.
>>> start_date
datetime.datetime(2021, 8, 20, 0, 0)
>>> end_date
datetime.datetime(2021, 9, 21, 0, 0)
В этом случае ожидаемый результат должен быть:
(-104 * (days between 08/20 and 08/25)) (-113 * (days between 08/26 and 09/21)
Это то, что у меня есть до сих пор:
(
my_product.income_streams
.values("product")
.filter(type=IncomeStream.Types.DAILY_EXPENSE)
.filter(add_to_commission_basis=True)
.annotate(period_expenses=Case(
When(Q(from_date__lte=start_date) amp; Q(to_date__lte=end_date),
then=ExpressionWrapper( start_date - F('to_date'), output_field=IntegerField()))
), # Other When cases...
)
) # Sum all period_expenses results and you've got the solution
И это то, что создает мне проблемы:
then=ExpressionWrapper( start_date - F('to_date'), output_field=IntegerField())
Это выражение всегда возвращает 0 (пожалуйста, обратите внимание, что именно поэтому я даже не пытаюсь умножить на value
: это был бы следующий шаг).
По-видимому start_date - F('to_date')
, это не то же самое, что «дайте мне разницу в днях между этими 2 датами».
Вы бы завершили это на Python с timedelta
помощью . Какой эквивалент в ORM?
Я пробовал с ExtractDay
:
then=ExpressionWrapper( ExtractDay(start_date - F('to_date'))
Но я понимаю: django.db.utils.OperationalError: user-defined function raised exception
А также попробовал с DurationField
:
then=ExpressionWrapper(start_date - F('to_date'), output_field=DurationField())
Но это также возвращает ноль: datetime.timedelta(0)
Ответ №1:
Приведение start_date
в а DateTimeField
решает проблему, а приведение разницы в а DurationField
-это следующий шаг.
Так:
Cast(Cast(start_date, output_field=DateTimeField()) - F('to_date'), output_field=DurationField())
Это будет хорошо работать на любой серверной части базы данных, но для того, чтобы получить разницу в днях, вам нужно обернуть ее в ExtractDay, что вызовет ValueError: Extract requires native DurationField database support.
, если вы используете SQLite.
Если вы привязаны к SQLite и не можете использовать ExtractDay, вы можете использовать микросекунды и преобразовать их вручную в дни, разделив на 86400000000
duration_in_microseconds=ExpressionWrapper(F('to_date') - (Cast(start_date, output_field=DateTimeField())), output_field=IntegerField())
Затем
.annotate(duration_in_days=ExpressionWrapper(F('period_duration_microseconds') / 86400000000, output_field=DecimalField())