Как вычесть timedelta из datetime в peewee?

#python #postgresql #peewee

#python #postgresql #peewee

Вопрос:

Рассмотрим следующие таблицы:

 class Recurring(db.Model):
    schedule    = ForeignKeyField(Schedule)
    occurred_at = DateTimeField(default=datetime.now)

class Schedule(db.Model):
    delay = IntegerField() # I would prefer if we had a TimeDeltaField
  

Теперь я хотел бы получить все те события, которые должны повторяться:

 query = Recurring.select(Recurring, Schedule).join(Schedule)
query = query.where(Recurring.occurred_at < now - Schedule.delay) # wishful
  

К сожалению, это не работает. Следовательно, в настоящее время я делаю что-то следующее:

 for schedule in schedules:
    then  = now - timedelta(minutes=schedule.delay)
    query = Recurring.select(Recurring, Schedule).join(Schedule)
    query = query.where(Schedule == schedule, Recurring.occurred_at < then)
  

Однако теперь вместо выполнения одного запроса я выполняю несколько запросов.

Есть ли способ решить вышеуказанную проблему только с помощью одного запроса? Одно из решений, о котором я подумал, было:

 class Recurring(db.Model):
    schedule     = ForeignKeyField(Schedule)
    occurred_at  = DateTimeField(default=datetime.now)
    repeat_after = DateTimeField() # repeat_after = occurred_at   delay

query = Recurring.select(Recurring, Schedule).join(Schedule)
query = query.where(Recurring.repeat_after < now)
  

Однако приведенная выше схема нарушает правила третьей нормальной формы.

Ответ №1:

Каждая база данных реализует разные функции добавления даты и времени, что отстойно. Так что это будет немного зависеть от того, какую базу данных вы используете.

Например, для postgres мы можем использовать помощник «интервал»:

 # Calculate the timestamp of the next occurrence. This is done
# by taking the last occurrence and adding the number of seconds
# indicated by the schedule. 
one_second = SQL("INTERVAL '1 second'")
next_occurrence = Recurring.occurred_at   (one_second * Schedule.delay)

# Get all recurring rows where the current timestamp on the
# postgres server is greater than the calculated next occurrence.
query = (Recurring
         .select(Recurring, Schedule)
         .join(Schedule)
         .where(SQL('current_timestamp') >= next_occurrence))

for recur in query:
    print(recur.occurred_at, recur.schedule.delay)
  

Вы также можете заменить объект datetime на «current_timestamp», если хотите:

 my_dt = datetime.datetime(2019, 3, 1, 3, 3, 7)
...
.where(Value(my_dt) >= next_occurrence)
  

Для SQLite вы бы сделали:

 # Convert to a timestamp, add the scheduled seconds, then convert back
# to a datetime string for comparison with the last occurrence.
next_ts = fn.strftime('%s', Recurring.occurred_at)   Schedule.delay
next_occurrence = fn.datetime(next_ts, 'unixepoch')
  

Для MySQL вы бы сделали:

 # from peewee import NodeList
nl = NodeList((SQL('INTERVAL'), Schedule.delay, SQL('SECOND')))
next_occurrence = fn.date_add(Recurring.occurred_at, nl)
  

И, наконец, я бы посоветовал вам попробовать лучшие имена для ваших моделей / полей. т.е. Schedule.interval вместо Schedule.задержка и повторяющийся.last_run вместо occurred_at.

Комментарии:

1. Спасибо за исчерпывающий ответ. Это было действительно полезно. Кстати, код в моем вопросе не является фактическим кодом, который я написал. Я просто сократил проблему до самого необходимого. Фактические имена, которые я использую, более описательны.