Python 炼金术比较两个相关表格的两个日期
我有两个相关的表,User和UserDownload,现在我想过滤掉它创建的UserDownload,它大于用户创建的UserDownload加上一天,所以python代码是:Python 炼金术比较两个相关表格的两个日期,python,date,sqlalchemy,Python,Date,Sqlalchemy,我有两个相关的表,User和UserDownload,现在我想过滤掉它创建的UserDownload,它大于用户创建的UserDownload加上一天,所以python代码是: result=db.session.query(UserDownload.uid).join(User,UserDownload.uid==User.id).filter(UserDownload.created\u at>=User.created\u at+timedelta(days=1)).all() 它的逻辑
result=db.session.query(UserDownload.uid).join(User,UserDownload.uid==User.id).filter(UserDownload.created\u at>=User.created\u at+timedelta(days=1)).all()
它的逻辑似乎正确,但结果很奇怪,有些结果,用户加上一天的创建时间比用户下载的创建时间少,但有些不是。我检查的原始sql查询字符串是:
选择user\u downloads.uid作为user\u downloads\n从user\u downloads加入user\u downloads.uid=users.id\n此处user\u downloads.created\u at>=users.created\u at+:created\u at\u 1
真的不知道:在1处创建意味着什么
例如,结果包含这样一个用户下载(我将UserDownload.uid替换为UserDownload,并通过其uid查询用户):
用户下载创建时间:datetime.datetime(2015,12,3,8,39,56)
user.created_于:datetime.datetime(2015,12,2,11,7,14)
根据您使用的后端,您需要您的过滤器来生成sql-like(用于mysql):
SQLAlchemy不会将datetime
对象和InstrumentedAttribute
对象之间的算术转换为DATE\u ADD
(或根据后端而定的等效)函数。因此,您可以通过以下方式进行过滤:
UserDownload.created_at >= User.created_at + timedelta(days=1)
它被转换为:
...WHERE user_downloads.created_at >= users.created_at + :created_at_1
其中,它将timedelta(days=1)
视为文本值,并对其进行参数化。这就是在_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)
我发现这个问题很有帮助:
来自评论
既然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完全相同,只是我们在用户上添加了一个值。在处创建,由绑定参数在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
的值添加到用户。在与user\u下载进行比较之前,created_at
。当我执行查询时,这是传递给服务器的参数值:
{'created_at_1': datetime.datetime(1970, 1, 2, 0, 0)}
因此,即使您向用户添加timedelta(days=1)
。在过滤器中创建时,SQLAlchemy实际上会传递一个datetime
对象,相当于+
,或者在本例中:datetime(1970,1,1,0,0)+timedelta(days=1)
或datetime(1970,1,2,0,0)
(这是您在上面的参数值字典中看到的值)
那么,user.created_at+datetime(1970,1,2,0,0)
的值到底是多少呢?我用created_at=datetime(1970,1,1,0,0)
向数据库中添加了一个user
实例:
然后运行此查询:
engine.execute(text("SELECT created_at + :a FROM users"), a=datetime(1970, 1, 2, 0, 0)).fetchall()
它返回:
[(19700101001970.0,)]
这是用户的值。在处创建,没有任何格式,日期时间(1970,1,2,0,0)的年份部分连接到末尾。因此,这就是您的查询与用户下载进行比较的地方。在处创建是为了(相对)简而言之,我不打算研究比较user\u download.created\u at
和该值是如何工作的,但希望我已经证明,您查询的最终结果不是比较user\u download.created\u at
和user.created\u at
加1天
当我使用诸如:User.query.filter(User.created\u 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
值进行简单比较。您使用的是什么数据库?@supershot我使用Mysql作为数据库,sqlalchemy作为flask.SQ中的ormLAlchemy==1.0.12谢谢,这很有帮助。但是我仍然有一个问题,因为mysql可以处理timedelta(days=1)参数,为什么我使用的查询会失败。顺便说一句,当我使用诸如:User.query.filter(User.created_at>datetime.now()+timedelta(days=1))这样的查询时,很好。这是因为你不能在python对象和数据库中的列之间添加日期吗?我添加到上面的答案是为了尝试解决你的额外问题。顺便说一句,19700101001970.0对我来说是原始日期加上19min和70秒。当我将原始数据迁移到mongo并通过聚合查询它时,它看起来就像原始查询筛选出用户下载量大于pl处用户创建的下载量
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)}