Совокупная разница между полями DateTime в Django - PullRequest
7 голосов
/ 08 декабря 2011

У меня есть таблица, содержащая ряд записей, которые относятся к периодам времени (в частности, время, отработанное для клиента):

task_time:
id     |    start_time    |    end_time       |    client (fk)
1        08/12/2011 14:48   08/12/2011 14:50     2

Я пытаюсь объединить все время, отработанное для данного клиента, из моего приложения Django:

time_worked_aggregate = models.TaskTime.objects.\
                        filter(client = some_client_id).\
                        extra(select = {'elapsed': 'SUM(task_time.end_time - task_time.start_time)'}).\
                        values('elapsed')

if len(time_worked_aggregate) > 0:
    time_worked = time_worked_aggregate[0]['elapsed'].total_seconds()
else:
    time_worked = 0

Это выглядит не элегантно, но действительно работает . Или, по крайней мере, я подумал: получается, что он отлично работает на базе данных PostgreSQL, но когда я перехожу на SQLite, все умирает.

Небольшое копание показывает, что причина этого в том, что DateTime не являются первоклассными данными в SQLite. Следующий необработанный запрос SQLite выполнит мою работу:

SELECT SUM(strftime('%s', end_time) - strftime('%s', start_time)) FROM task_time WHERE ...;

У меня такой вопрос:

  • Пример Python выше кажется окольным. Можем ли мы сделать это более элегантно?
  • Что еще более важно на данном этапе, можем ли мы сделать это так, чтобы это работало как на Postgres, так и на SQLite? В идеале я бы не хотел писать необработанные SQL-запросы и включать серверную часть базы данных, которая оказывается на месте; в общем Джанго очень хорошо защищает нас от этого. Есть ли у Django разумная абстракция для этой операции? Если нет, то какой для меня разумный способ сделать условное переключение на бэкэнде?

Я должен упомянуть для контекста, что в наборе данных много тысяч записей; следующее не очень практично:

sum([task_time.end_date - task_time.start_date for task_time in models.TaskTime.objects.filter(...)])

Ответы [ 3 ]

8 голосов
/ 03 февраля 2017

Почти то же решение, что и @andri.В конечном результате вы получите те же данные. ExpressionWrapper - Новое в Django 1.8.

from datetime import timedelta
from django.db.models import ExpressionWrapper, F, fields
from app.models import MyModel

duration = ExpressionWrapper(F('closed_at') - F('opened_at'), output_field=fields.DurationField())
objects = MyModel.objects.closed().annotate(duration=duration).filter(duration__gt=timedelta(seconds=2))

for obj in objects:
    print obj.id, obj.duration, obj.duration.seconds

# sample output
# 807 0:00:57.114017 57
# 800 0:01:23.879478 83
# 804 3:40:06.797188 13206
# 801 0:02:06.786300 126
3 голосов
/ 01 марта 2016

Я думаю, что с Django 1.8 мы можем сделать лучше:

Я хотел бы просто нарисовать часть с аннотацией, дальнейшая часть с агрегацией должна быть простой:

from django.db.models import F, Func
SomeModel.objects.annotate(
    duration = Func(F('end_date'), F('start_date'), function='age')
)

[подробнее о функции postgres age здесь: http://www.postgresql.org/docs/8.4/static/functions-datetime.html]

каждый экземпляр SomeModel будет аннотирован с duration разницей во времени для поля, которая в python будет datetime.timedelta() объектом [подробнее о datetime timedelta здесь: https://docs.python.org/2/library/datetime.html#timedelta-objects]

0 голосов
/ 08 декабря 2011

Django в настоящее время поддерживает только агрегаты для Min, Max, Avg и Count, поэтому использование необработанного SQL - единственный способ достичь того, чего вы хотите. Когда вы используете сырой SQL, независимость от базы данных выходит за рамки, поэтому, к сожалению, вам не повезло. Вам нужно будет просто обнаружить базу данных и соответствующим образом изменить SQL.

...