Группировка по дате в Django
Я пытаюсь добиться результата SQL запросом
SELECT
UNIX_TIMESTAMP(DATE((FROM_UNIXTIME(`timestamp`)))) AS `x`,
COUNT(`timestamp`) as y
FROM somedb.events
WHERE user_id=3 AND
`timestamp` > 1612117800 AND
`timestamp` < 1614450600 AND
`kind`='food'
GROUP BY `x`
ORDER BY `x` desc;
используя Django ORM. Ожидаемый результат:
[
{
"x": 1613759400,
"y": 2
},
{
"x": 1612463400,
"y": 1
}
]
Вот что я пробовал до сих пор:
queryset = events.objects.filter(
user=request.user,
timestamp__range=dates,
kind=self.model.FOOD
)
result = queryset.annotate(
trunc_date_timestamp=Func(
Value(
TruncDate(
Func(
F('timestamp'),
function='FROM_UNIXTIME',
)
)
),
function='UNIX_TIMESTAMP',
output_field=models.IntegerField()
)
).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')
В результате получается вывод:
[
{
"x": 0,
"y": 3
}
]
и
result = queryset.annotate(
trunc_date_timestamp=Func(
Func(
F('timestamp'),
function='FROM_UNIXTIME',
output_field=models.DateField()
),
function='UNIX_TIMESTAMP'
)
).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')
производит вывод:
[
{
"x": 1613831760,
"y": 1
},
{
"x": 1613810160,
"y": 1
},
{
"x": 1612520520,
"y": 1
}
]
Наконец-то я заставил его работать. Я допустил крошечные ошибки в обоих подходах.
Первый подход:
result = ueryset.annotate(
trunc_date_timestamp=Func(
TruncDate(
Func(
F('timestamp'),
function='FROM_UNIXTIME',
output_field=models.DateField() # <<--- This is must
)
),
function='UNIX_TIMESTAMP',
)
).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')
Удалено API Value()
и добавлено output_field=models.DateTimeField()
к внутреннему API Func()
. output_field
будет гарантировать, что поле, возвращаемое внутренним Func()
API, должно быть типа models.DateField()
, к которому будет применено TruncDate
.
Второй подход:
queryset = queryset.annotate(
trunc_date_timestamp=Func(
Cast(
Func(
F('timestamp'),
function='FROM_UNIXTIME',
),
output_field=models.DateField()
),
function='UNIX_TIMESTAMP'
)
).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')
Изначально я думал
Func(
F('timestamp'),
function='FROM_UNIXTIME',
output_field=models.DateField()
)
это вернет поле типа models.DateField()
, но я не знаю, почему у меня не получилось и не сработало!!! Поэтому я использовал вместо этого метод Cast()
и привел возвращаемое выражение к models.DateField()
, чтобы заставить его работать
queryset = queryset.annotate(
trunc_date_timestamp=Func(
Cast(
Func(
F('timestamp'),
function='FROM_UNIXTIME',
),
output_field=models.DateField()
),
function='UNIX_TIMESTAMP'
)
).values(x=F('trunc_date_timestamp')).annotate(y=models.Count('x')).order_by('-x')
Хотя это решение работает, я чувствую, что оригинальный код для второго подхода также должен работать, потому что если я правильно понял выражение Func()
, то оба из
Func(
F('timestamp'),
function='FROM_UNIXTIME',
output_field=models.DateField()
)
и
Cast(
Func(
F('timestamp'),
function='FROM_UNIXTIME'
),
output_field=models.DateField()
)
выполняют одну и ту же работу.