我们有sqlite数据库,日期时间实际上是以Excel格式存储的(这有一个很好的理由;它是我们系统的标准表示选择,sqlite数据库可以被多种语言/系统访问)
近几个月来,Python已经成功地引入了Python,并且SQLAlchemy就是其中的一部分。 sqlite3 dbapi层快速绑定SQLite缺少给定SQL函数的自定义Python函数的能力特别受欢迎。
我写了一个ExcelDateTime类型装饰器,从sqlite数据库中检索结果集时工作正常; Python可以恢复正确的日期时间。
但是,我有一个真正的问题是绑定自定义python函数,它们希望输入参数是python日期时间;我以为这就是bindparam的用途,但我显然错过了一些东西,因为我不能让这个场景起作用。不幸的是,修改函数以从excel datetimes转换为python datetimes不是一种选择,也不会改变数据库中日期时间的表示,因为不止一种系统/语言可以访问它。
下面的代码是一个独立的示例,可以“按原样”运行,并代表问题。自定义函数“get_month”已创建,但失败,因为它接收原始数据,而不是“Born”列中的类型转换数据。最后你可以看到我到目前为止所尝试的内容,以及它吐出的错误......
我想做什么不可能?或者是否有一种不同的方法来确保绑定函数接收适当的python类型?这是迄今为止我唯一无法解决的问题,很高兴找到解决方案!
import sqlalchemy.types as types
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData
from sqlalchemy.sql.expression import bindparam
from sqlalchemy.sql import select, text
from sqlalchemy.interfaces import PoolListener
import datetime
# setup type decorator for excel<->python date conversions
class ExcelDateTime( types.TypeDecorator ):
impl = types.FLOAT
def process_result_value( self, value, dialect ):
lxdays = int( value )
lxsecs = int( round((value-lxdays) * 86400.0) )
if lxsecs == 86400:
lxsecs = 0
lxdays += 1
return ( datetime.datetime.fromordinal(lxdays+693594)
+ datetime.timedelta(seconds=lxsecs) )
def process_bind_param( self, value, dialect ):
if( value < 200000 ): # already excel float?
return value
elif( isinstance(value,datetime.date) ):
return value.toordinal() - 693594.0
elif( isinstance(value,datetime.datetime) ):
date_part = value.toordinal() - 693594.0
time_part = ((value.hour*3600) + (value.minute*60) + value.second) / 86400.0
return date_part + time_part # time part = day fraction
# create sqlite memory db via sqlalchemy
def get_month( dt ):
return dt.month
class ConnectionFactory( PoolListener ):
def connect( self, dbapi_con, con_record ):
dbapi_con.create_function( 'GET_MONTH',1,get_month )
eng = create_engine('sqlite:///:memory:',listeners=[ConnectionFactory()])
eng.dialect.dbapi.enable_callback_tracebacks( 1 ) # show better errors from user functions
meta = MetaData()
birthdays = Table('Birthdays', meta, Column('Name',String,primary_key=True), Column('Born',ExcelDateTime), Column('BirthMonth',Integer))
meta.create_all(eng)
dbconn = eng.connect()
dbconn.execute( "INSERT INTO Birthdays VALUES('Jimi Hendrix',15672,NULL)" )
# demonstrate the type decorator works and we get proper datetimes out
res = dbconn.execute( select([birthdays]) )
tuple(res)
# >>> ((u'Jimi Hendrix', datetime.datetime(1942, 11, 27, 0, 0)),)
# simple attempt (blows up with "AttributeError: 'float' object has no attribute 'month'")
dbconn.execute( text("UPDATE Birthdays SET BirthMonth = GET_MONTH(Born)") )
# more involved attempt( blows up with "InterfaceError: (InterfaceError) Error binding parameter 0 - probably unsupported type")
dbconn.execute( text( "UPDATE Birthdays SET BirthMonth = GET_MONTH(:Born)",
bindparams=[bindparam('Born',ExcelDateTime)],
typemap={'Born':ExcelDateTime} ),
Born=birthdays.c.Born )
非常感谢。
答案 0 :(得分:1)
不要让Excel / Microsoft决定如何存储日期/时间,而是依靠标准/“显而易见”的做法来减少麻烦和工作。
根据域名标准处理对象--Python在SQL / SQLAlchemy中的方式(日期时间对象),SQL在SQLite中的方式(本机日期/时间类型而不是浮点数!)。
使用API在域之间进行必要的转换。 (Python通过SQLAlchemy与SQLite交谈,Python通过xlrd/xlwt与Excel对话,Python与其他系统对话,Python是你的粘合剂。)
在SQLite中使用标准日期/时间类型允许您以标准可读方式编写SQL而不涉及Python(WHERE date BETWEEN '2011-11-01' AND '2011-11-02'
比WHERE date BETWEEN 48560.9999 AND 48561.00001
更有意义)。当您的应用程序/数据库需要增长时,它允许您轻松地将其移植到另一个DBMS(无需重写所有这些特殊功能)。
在Python中使用本机日期时间对象允许您使用大量免费,经过良好测试和非EEE(包含,扩展,熄灭)API。 SQLAlchemy就是其中之一。
我希望您知道Mac和Windows中Excel日期时间浮动之间存在轻微但危险的区别吗?谁知道你的一个客户将来会从Mac提交一个Excel文件并使你的应用程序崩溃(实际上,更糟糕的是他们突然从错误中赚了一百万美元)?
因此,我建议您在使用Python处理Excel时使用xlrd/xlwt(还有另一个用于阅读Excel 2007的程序包)并让SQLALchemy和您的数据库使用标准的日期时间类型。但是,如果您坚持继续将日期时间存储为Excel float,则可以节省大量时间来重用xlrd/xlwt中的代码。它具有将Python对象转换为Excel数据的功能,反之亦然。
编辑:为了清晰起见......
从数据库读取到Python没有问题,因为你有一个将float转换为Python日期时间的类。
您有问题通过SQLAlchemy或使用其他本机Python函数/模块/扩展写入数据库,因为您在期望标准Python时试图强制使用非标准类型日期时间。从视角来看, ExcelDateTime类型是一个浮点数,而不是日期时间。
虽然Python使用动态/鸭子类型,但仍然是强类型。它不允许你做“无意义/愚蠢”,如将字符串添加到字符串,或强制浮动日期时间。
至少有两种解决方法:
声明自定义类型 - 似乎是您想要的路径。不幸的是,这是艰难的方式。创建一个类型是非常困难的,它也可以假装是日期时间。可能,是的,但需要对类型仪器进行大量研究。对不起,您必须自己填写相关文档。
创建实用程序功能 - 应该是更简单的方法,恕我直言。您需要2个函数:a)float_to_datetime()用于从数据库转换数据以返回Python日期时间,b)datetime_to_float()用于将Python日期时间转换为Excel float。
关于解决方案#2,正如我所说,您可以通过重用xlrd/xlwt中的 xldate_from_datetime_tuple()来简化您的生活。该功能“将日期时间元组(年,月,日,小时,分钟,秒)转换为Excel日期值。”安装xlrd然后转到/ path_to_python / lib / site-packages / xlrd。该函数位于xldate.py中 - 对于理解来说,源代码已有详细记录。