В зависимости от того, какой сервер вы используете, вам нужен ваш фильтр для генерации sql (для mysql):
...WHERE user_downloads.created_at >= DATE_ADD(users.created_at, INTERVAL 1 DAY)
SQLAlchemy не преобразует арифметику между объектом datetime
и InstrumentedAttribute
объект в этой DATE_ADD
(или эквивалентной в зависимости от бэкэнда) функции.Так что, где вы фильтруете это:
UserDownload.created_at >= User.created_at + timedelta(days=1)
, он преобразуется в это:
...WHERE user_downloads.created_at >= users.created_at + :created_at_1
, где он обрабатывает timedelta(days=1)
как буквальное значение и параметризует его.Это то, чем является :created_at_1
, параметр, который занимает место в запросе для объекта timedelta
, который будет передан вместе с запросом на сервер (как примечание, в MySQL, что timedelta
фактически конвертируетсяв datetime
объект, который имеет эпоху + 1 день, поскольку MySQL не имеет собственного типа INTERVAL
).
Поэтому, чтобы запрос выполнялся так, как вам нужно, вам нужно использовать sqlalchemy.func
объект для генерации нужной вам функции на стороне сервера.Продолжая с примером MySQL, соответствующий запрос может быть:
from sqlalchemy import func, text
q = session.query(UserDownload.uid).\
join(User, UserDownload.uid == User.id).\
filter(
UserDownload.created_at >=
func.DATE_ADD(
User.created_at,
text('INTERVAL 1 DAY')
)
)
, который генерирует:
SELECT user_downloads.uid
FROM user_downloads INNER JOIN users ON user_downloads.uid = users.id
WHERE user_downloads.created_at >= DATE_ADD(users.created_at, INTERVAL 1 DAY)
Мне показался этот вопрос полезным: Использование DATEADD в sqlalchemy
Из комментариев
, поскольку mysql может обрабатывать параметры timedelta (days = 1), почему использованный мной запрос завершается неудачей.
Хорошо, я постараюсь более подробно рассказать о вашем исходном запросе, но дайте мне немного свободы, пока я работаю над этим.Давайте на секунду забудем о timedelta
и посмотрим, что сгенерировал sql.Так вот:
session.query(UserDownload.uid).join(User, UserDownload.uid == User.id).filter(UserDownload.created_at >= User.created_at)
генерирует этот sql:
SELECT user_downloads.uid AS user_downloads_uid
FROM user_downloads INNER JOIN users ON user_downloads.uid = users.id
WHERE user_downloads.created_at >= users.created_at
Ничего сложного там не получится.Теперь, когда мы добавляем обратно в timedelta
, сгенерированный sql точно такой же, за исключением того, что мы добавляем значение в users.created_at
, которое представлено параметром bind created_at_1
:
SELECT user_downloads.uid AS user_downloads_uid
FROM user_downloads INNER JOIN users ON user_downloads.uid = users.id
WHERE user_downloads.created_at >= users.created_at + %(created_at_1)s
сравнение выполняется первым или сложение?Выполнение этого запроса ...
print(engine.execute(text("SELECT 3 >= 2 + 2")).fetchall())
... возвращает 0
(то есть False
), что доказывает, что сложение (2 + 2
) разрешено перед сравнением (>=
).Поэтому можно с уверенностью предположить, что это происходит и в вашем запросе, и поэтому значение created_at_1
добавляется к users.created_at
до по сравнению с user_downloads.created_at
.Когда я выполняю ваш запрос, это значение параметра, передаваемое на сервер:
{'created_at_1': datetime.datetime(1970, 1, 2, 0, 0)}
Так что даже если вы добавляете timedelta(days=1)
к users.created_at
в своем фильтре, SQLAlchemy фактически передает datetime
эквивалент объектана <epoch> + <timedelta>
, или в этом случае: datetime(1970, 1, 1, 0, 0) + timedelta(days=1)
или datetime(1970, 1, 2, 0, 0)
(это значение, которое вы видите в словаре значений параметров выше).
Так что же является значением user.created_at + datetime(1970, 1, 2, 0, 0)
?Я добавил User
экземпляр в базу данных с помощью created_at = datetime(1970, 1, 1, 0, 0)
:
session.add(User(id=1, created_at=datetime(1970, 1, 1, 0, 0)))
session.commit()
Затем запустил этот запрос:
engine.execute(text("SELECT created_at + :a FROM users"), a=datetime(1970, 1, 2, 0, 0)).fetchall()
, который возвратил:
[(19700101001970.0,)]
Это значение user.created_at
без какого-либо форматирования, причем годовая часть datetime(1970, 1, 2, 0, 0)
конкатенируется до конца.Вот что ваш запрос сравнивает с user_download.created_at
.Ради (относительной) краткости я не собираюсь рассматривать, как работает сравнение user_download.created_at
и этого значения, но, надеюсь, я продемонстрировал, что конечный результат вашего запроса не сравнивает user_download.created_at
с users.created_at
плюс 1 день.
, когда я использую запрос, например: User.query.filter (User.created_at> datetime.now () + timedelta (days = 1)), он работает нормально.
Используя этот запрос, вот сгенерированный sql:
SELECT users.id AS users_id, users.created_at AS users_created_at
FROM users
WHERE users.created_at > %(created_at_1)s
и значение, переданное на сервер:
{'created_at_1': datetime.datetime(2018, 11, 21, 21, 38, 51, 670890)}
Итак, вы можете увидетьчто часть datetime + timedelta
разрешается до передачи в базу данных и, таким образом, операция базы данных представляет собой простое сравнение столбца DATETIME
со значением datetime
.