Flask Sqlalchemy比较两个相关表的两个日期

时间:2018-11-20 07:46:54

标签: python date sqlalchemy

我有两个相关的表,User和UserDownload,现在我想过滤掉它的created_at大于用户的created_at再加上一天的UserDownload,因此python代码是:

result = db.session.query(UserDownload.uid).join(User, UserDownload.uid == User.id).filter(UserDownload.created_at >= User.created_at + timedelta(days=1)).all()

逻辑上似乎是正确的,但是结果很奇怪,有些结果是,用户的创建时间加上一天的时间比UserDownload的created_at少,但是有些却不是。我检查查询字符串的原始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,并通过其uid查询User):

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 :(得分:1)

根据您使用的后端,您需要过滤器来生成类似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中,{{ 1}}实际上被转换为一个历时+ 1天的timedelta对象,因为MySQL没有本机的datetime类型)。

因此,要使查询执行所需的操作,您需要使用INTERVAL对象来生成所需的服务器端函数。继续以MySQL示例为例,适当的查询可能是:

sqlalchemy.func

哪个生成:

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')
        )
    )

我发现这个问题有帮助:Using DATEADD in sqlalchemy

来自评论

  

由于mysql可以处理timedelta(days = 1)参数,为什么我使用查询   失败。

好的,我将尝试更深入地了解您的原始查询,但是在我逐步解决此问题时,请给我一些自由。让我们忘记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) 一秒钟,然后看看生成的sql是什么。所以这个:

timedelta

生成此sql:

session.query(UserDownload.uid).join(User, UserDownload.uid == User.id).filter(UserDownload.created_at >= User.created_at)

没什么好抱怨的。现在,当我们重新添加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 时,生成的sql完全相同,只是我们在timedelta上添加了一个由绑定参数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),SQLAlchemy实际上也会传递一个与users.created_at等效的datetime对象,或者在这种情况下:{{1} }或<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)与该值的比较是如何工作的,但是希望我已证明您查询的最终结果不是比较{{1 }}和user_download.created_at加上1天。

  

当我使用以下查询时:User.query.filter(User.created_at>   datetime.now()+ timedelta(days = 1)),效果很好。

使用该查询,这是生成的sql:

user_download.created_at

,传递给服务器的值为:

user_download.created_at

因此,您可以看到users.created_at部分在传递给数据库之前 已解决,因此,数据库操作是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)} 值。