我有两个相关的表,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)
答案 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)}
值。