Настой Sqlalchemy сравнить две даты двух связанных таблиц - PullRequest
0 голосов
/ 20 ноября 2018

У меня есть две связанные таблицы, User и UserDownload, теперь я хочу отфильтровать UserDownload, для которого он создан - он больше, чем create_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()

логика кажется правильной, но результат странный, какой-то результат, время создания пользователя плюс один день меньше, чем create_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

действительно не знаю, что: созданный_ат_1 означает.

например, результат содержит такую ​​user_download (я заменяю UserDownload.uid на UserDownload и запрашиваю пользователя по его uid):

user_download.created_at: datetime.datetime(2015, 12, 3, 8, 39, 56) user.created_at: datetime.datetime(2015, 12, 2, 11, 7, 14)

1 Ответ

0 голосов
/ 20 ноября 2018

В зависимости от того, какой сервер вы используете, вам нужен ваш фильтр для генерации 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...