Использование ORM django для усреднения по полю postgres "timestamp without time zone"
Я пытаюсь усреднить поле postgres timestamp without time zone
с помощью отличного ORM от django следующим образом:
from django.db.models import Avg
ModelName.objects.filter(a_field='some value').aggregate(Avg('time'))
Однако я получаю:
function avg(timestamp without time zone) does not exist
LINE 1: SELECT AVG("model_name"."time") AS "time__avg" FROM "m...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
Есть ли способ сделать это с помощью ORM от django?
Если нет, то как это обойти?
У меня была похожая проблема, когда я хотел найти среднее время, затраченное на голосование за определенный пункт. Но postgres не позволял брать среднее значение времени по датам. Это приводило к следующей ошибке:
django.db.utils.ProgrammingError: function avg(timestamp with time zone) does not exist
LINE 1: SELECT "votes_item"."name", AVG("votes_vote"."datetime") AS ...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
Для упрощения рассмотрим следующие таблицы, где Vote имеет внешнее ключевое отношение к Item:
Таблица предметов:
id: pK | name: char | datetime(time at which the item was inserted) |
---|---|---|
1 | Apple | 22-06-23 11:25:33 |
2 | Orange | 22-06-22 01:22:18 |
Таблица голосования:
id: pK | user: Fk (user who voted for the item) | item: Fk (the item the user voted) | vote: (1 for positive vote and -1 negative vote) | datetime (time at which the item was voted) |
---|---|---|---|---|
1 | 1 | 1 | 1 | 2022-06-22 11:26:18 |
2 | 3 | 1 | 1 | 2022-06-21 12:26:36 |
3 | 2 | 1 | 1 | 2022-06-26 01:20:59 |
Я хотел узнать среднее время, в течение которого пользователи голосовали за каждый элемент. Например: все среднее время, затраченное пользователями на голосование Apple (т.е. аннотирование)
Поскольку функция postgres avg не принимает данные непосредственно в формате datetime, сначала преобразуйте их в секунды, затем возьмите среднее значение и преобразуйте его обратно в формат datetime.
Чтобы упростить ситуацию, создайте два класса, как показано ниже.
from django.db import models
class Epoch(models.expressions.Func):
template = 'EXTRACT(epoch FROM %(expressions)s)::FLOAT'
output_field = models.FloatField()
class DateTimeFromFloat(models.expressions.Func):
template = 'To_TIMESTAMP(%(expressions)s)::TIMESTAMP at time zone \'UTC\''
output_field = models.DateTimeField()
читайте больше о Func
в этом отличном ответе
Теперь я хотел получить среднее время, в течение которого каждый пункт был проголосован положительно
Поэтому я использую
Item.objects.filter(vote__vote=1).annotate(avg_time=DateTimeFromFloat(Avg(Epoch('vote__datetime')))).values('avg_time', 'name')
важная часть:
annotate(avg_time=DateTimeFromFloat(Avg(Epoch('vote__datetime'))))
output
<QuerySet [{'name': 'Apple', 'avg_time': datetime.datetime(2022, 6, 23, 8, 24, 37, 666667, tzinfo=datetime.timezone.utc)}]>
Вы можете выполнить аналогичную операцию, используя aggregate
.