У меня есть две связанные таблицы, User и UserDownload, теперь я хочу отфильтровать UserDownload, у которого created_at больше, чем created_at пользователя плюс один день, поэтому код python:
result = db.session.query(UserDownload.uid).join(User, UserDownload.uid == User.id).filter(UserDownload.created_at >= User.created_at + timedelta(days=1)).all()
логика кажется правильной, но результат странный, какой-то результат, время создания пользователя плюс один день меньше, чем created_at для UserDownload, но некоторые - нет. и необработанный sql, который я проверяю, строка запроса:
SELECT user_downloads.uid AS user_downloads_uid \nFROM user_downloads JOIN users ON user_downloads.uid = users.id \nWHERE user_downloads.created_at >= users.created_at + :created_at_1
действительно не знаю, что означает created_at_1.
например, результат содержит такой user_download (я заменяю UserDownload.uid на UserDownload и запрашиваю User по его uid):
user_download.created_at: datetime.datetime(2015, 12, 3, 8, 39, 56)
user.created_at: datetime.datetime(2015, 12, 2, 11, 7, 14)
@SuperShoot Я использую Mysql в качестве базы данных, а sqlalchemy - в качестве орма во флаконе. SQLAlchemy == 1.0.12






В зависимости от используемого вами бэкэнда вам понадобится ваш фильтр для генерации 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
Из комментариев
since mysql can handle timedelta(days=1) params, why the query I used fails.
Хорошо, я постараюсь более подробно остановиться на вашем исходном запросе, но дайте мне немного свободы, поскольку я работаю над этим по ходу дела. Давайте на секунду забудем о 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, которое представлено параметром связывания 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 день.
when I use query like: User.query.filter(User.created_at > datetime.now() + timedelta(days=1)), it works fine.
Используя этот запрос, вот сгенерированный 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.
Спасибо, это помогает. Но у меня все еще есть вопрос, поскольку mysql может обрабатывать параметры timedelta (days = 1), почему запрос, который я использовал, не работает. Кстати, когда я использую запрос типа: User.query.filter (User.created_at> datetime.now () + timedelta (days = 1)), он отлично работает. Это потому, что вы не можете добавить дату между объектом python и столбцом в базе данных?
Я добавил к ответу выше, чтобы попытаться ответить на ваши дополнительные вопросы.
кстати, 19700101001970.0, для меня означает исходную дату плюс 19 минут и 70 секунд. и когда я переношу исходные данные в mongo и запрашиваю их по агрегату, кажется, что исходный запрос отфильтровывает user_download больше, чем user created_at плюс около 20 минут.
Какую базу данных вы используете?