【问题标题】:Have an SQLAlchemy + SQLite "create_function" issue with datetime representations日期时间表示存在 SQLAlchemy + SQLite“create_function”问题
【发布时间】:2011-12-25 22:37:30
【问题描述】:

我们有 sqlite 数据库,而日期时间实际上是以 Excel 格式存储的(这是有充分理由的;这是我们系统选择的标准表示,并且 sqlite 数据库可以被多种语言/系统访问)

最近几个月一直在将 Python 引入其中并取得了巨大成功,SQLAlchemy 就是其中的一部分。 sqlite3 dbapi 层能够在 SQLite 缺少给定 SQL 函数的情况下快速绑定自定义 Python 函数,这一点特别值得赞赏。

我写了一个 ExcelDateTime 类型的装饰器,在从 sqlite 数据库中检索结果集时效果很好; Python 得到正确的日期时间。

但是,我在绑定自定义 python 函数时遇到了真正的问题,这些函数期望输入参数是 python 日期时间;我原以为这就是 bindparam 的用途,但我显然遗漏了一些东西,因为我无法让这种情况发挥作用。不幸的是,修改函数以从 excel 日期时间转换为 python 日期时间不是一种选择,也不能更改数据库中日期时间的表示,因为可能有多个系统/语言可以访问它。

下面的代码是一个独立的示例,可以“按原样”运行,并且代表了问题。自定义函数“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 )

非常感谢。

【问题讨论】:

    标签: python sqlite datetime sqlalchemy


    【解决方案1】:

    与其让 Excel/Microsoft 决定您如何存储日期/时间,不如依靠标准/“显而易见的方式”来做事,这对您来说会更少麻烦和更有效。

    1. 根据其领域的标准处理对象 - Python/SQLAlchemy 中的 Python 方式(日期时间对象),SQLite 中的 SQL 方式(本机日期/时间类型而不是浮点数!)。

    2. 使用 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 就是其中之一。

    我希望您知道 Excel 日期时间浮点数在 Mac 和 Windows 中的细微但危险的区别?谁知道您的一位客户将来会从 Mac 提交 Excel 文件并导致您的应用程序崩溃(实际上,更糟糕的是他们突然从错误中赚取了一百万美元)?

    所以我的建议是你在从 Python 处理 Excel 时使用xlrd/xlwt(那里有另一个用于读取 Excel 2007 的包)并让 SQLALchemy 和你的数据库使用标准日期时间类型。但是,如果您坚持继续将日期时间存储为 Excel 浮点数,则可以节省大量时间来重用 xlrd/xlwt 中的代码。它具有将 Python 对象转换为 Excel 数据的功能,反之亦然。

    编辑:为清楚起见...

    从数据库读取到 Python 没有问题,因为您有将浮点数转换为 Python 日期时间的类。

    遇到问题通过 SQLAlchemy 或使用其他本机 Python 函数/模块/扩展写入数据库,因为当他们期望标准 Python 时,您试图强制使用非标准类型日期时间从 Python 的角度来看 ExcelDateTime 类型是浮点数,而不是日期时间。

    尽管 Python 使用动态/鸭子类型,它仍然是强类型。它不允许您做“废话/愚蠢”,例如将整数添加到字符串,或强制日期时间浮点数

    至少有两种方法可以解决这个问题:

    1. 声明自定义类型 - 似乎是您想要采取的路径。不幸的是,这是困难的方式。创建一个浮点类型也可以伪装成日期时间是相当困难的。可能,是的,但需要对类型仪器进行大量研究。抱歉,你必须自己去了解文档。

    2. 创建实用函数 - 应该是更简单的方法,恕我直言。您需要 2 个函数:a) float_to_datetime() 用于将数据库中的数据转换为 Python 日期时间,b) datetime_to_float() 用于将 Python 日期时间转换为 Excel 浮点数。

    关于解决方案 #2,正如我所说,您可以通过重用来自 xlrd/xlwtxldate_from_datetime_tuple() 来简化您的生活。该函数“将日期时间元组(年、月、日、小时、分钟、秒)转换为 Excel 日期值。”安装 xlrd 然后转到 /path_to_python/lib/site-packages/xlrd。该函数在 xldate.py 中 - 源代码有据可查,便于理解。

    【讨论】:

    • 如上所述,不能更改数据格式;我相信我提到过其他系统与这些数据库交互。 Python 绑定是现有基础架构的新增功能。我也知道 Mac 偏移量不同;这不是问题,因为 Excel 既不生成也不使用这些数据;完成这项工作的日期库是标准化的跨平台 C++,恰好使用与 Excel 相同的存储格式,但在所有执行平台(主要是 windows 和 linux)上始终保持一致
    • 此外,还有一个允许惯用查询的抽象层(例如:“DateCol > '2011-10-09'”),因此任何数据库移动都很容易进行,因为它只需要对抽象层的调整。尝试将数据作为 Python 日期时间呈现给绑定函数的目的是完全正确的,这样我们就可以利用所有期望 Python 日期时间的现有 Python 函数。简而言之,我们有一个现有的、健壮的、高性能的系统,我们希望通过一流的 Python 支持来扩展它。我们不打算重写系统的其余部分以适应 Python ;)
    • 另外一件小事;不幸的是,没有标准的 SQLite 日期时间类型。事实上,SQLite DATETIME 列的列亲和性实际上是 NUMERIC ...通常只返回'2011',其余的在数字强制期间被吃掉。缺少正确的 DATETIME 类型确实是 SQLite 唯一的主要缺陷...:\
    • 我已经扩展了我的答案来解决您的问题。
    • 谢谢;我认为您认为我的问题与我实际询问的问题不同。我们从任何 Python 函数写入数据库都没有问题。处理所有类型转换。 ONLY 剩下的问题是无法在数据库的“UPDATE”语句中使用 Python 绑定函数。其他一切都非常好,并且完全透明。我将澄清:“是否有可能使用 SQLAlchemy 将 TypeDecorated ExcelDateTimes 以将 Python 函数绑定为 python 日期时间”。由于它可以强制 SELECT 输出,我认为它会是......
    猜你喜欢
    • 2017-03-24
    • 2013-08-10
    • 2011-09-25
    • 2010-11-26
    • 1970-01-01
    • 2019-01-19
    • 2019-12-10
    • 2021-04-06
    • 2020-07-24
    相关资源
    最近更新 更多