Flask炼金术比较两个相关表格的两个日期

2024-04-19 22:20:43 发布

您现在位置:Python中文网/ 问答频道 /正文

我有两个相关的表,User和UserDownload,现在我想过滤掉UserDownload,它创建的\u at大于用户创建的\u 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()

逻辑似乎是正确的,但结果很奇怪,有些结果,用户加一天的创建时间比用户下载的创建时间少,而有些则不然。我检查查询字符串的原始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的意思。在

例如,结果包含这样一个用户的下载(我替换用户下载.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)


Tags: 代码用户iddbuiddatetimedownloads时间
1条回答
网友
1楼 · 发布于 2024-04-19 22:20:43

根据您所使用的后端,您需要您的过滤器来生成类似sql的(对于mysql):

...WHERE user_downloads.created_at >= DATE_ADD(users.created_at, INTERVAL 1 DAY)

SQLAlchemy不会将datetime对象和InstrumentedAttribute对象之间的算术转换为DATE_ADD(或根据后端而定的等效函数)。所以你可以用这个来过滤:

^{pr2}$

它被转换为:

...WHERE user_downloads.created_at >= users.created_at + :created_at_1

其中,它将timedelta(days=1)视为一个文本值,并对其进行参数化。这就是:created_at_1是一个参数,它在查询中将与查询一起传递给服务器的timedelta对象保留在查询中的位置(作为补充说明,在MySQL中,timedelta实际上被转换成一个datetime对象,这是epoch+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)

我发现这个问题很有帮助:Using DATEADD in 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上添加了一个值,该值由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_atprevior中,与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)或{}(这是您在上面的参数值字典中看到的值)。在

那么user.created_at + datetime(1970, 1, 2, 0, 0)的值到底是多少?我用User向数据库中添加了一个User实例:

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与{}进行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值的简单比较。在

相关问题 更多 >