SQLAlchemy反射查询与原生SQL查询SQLite NUMERIC datetime结果不一致问题
这个问题的核心在于SQLAlchemy对SQLite NUMERIC类型的反射逻辑,以及它如何处理结果集中的数据转换。让我们一步步拆解:
1. 为什么反射构造的查询会失败?
当你使用autoload=True反射表结构时,SQLAlchemy会根据SQLite返回的表元数据推断列类型。SQLite的NUMERIC是一个通用类型,但SQLAlchemy的SQLite方言默认会把它映射为DECIMAL(对应Python的decimal.Decimal类型)。
而你实际存储的是ISO格式的datetime字符串('2017-08-03 01:11:31'),当SQLAlchemy尝试把这个字符串转换为Decimal类型时,自然会抛出TypeError: must be real number, not str——因为字符串无法直接转成十进制数字。
那前两个查询为什么正常?
- 原生SQL查询:直接执行原始SQL,SQLAlchemy不会对结果做任何类型转换,直接返回SQLite返回的原始字符串。
- 手动构造selectable:用
text()包裹列和表名时,SQLAlchemy同样不会推断列类型,结果以原始字符串返回,跳过了类型转换步骤。
你看到生成的SQL语句和原生一致,这没错,但SQLAlchemy在结果处理阶段的逻辑完全不同——反射后的表列带有明确的类型定义,会触发自动类型转换。
2. 解决方案
这里有几个可行的修复方案:
方案一:反射时手动指定列类型
在反射表的时候,手动覆盖datetime列的类型为String或者DateTime:
m = sa.MetaData() table = sa.Table( 'testtable', m, sa.Column('datetime', sa.String), # 手动指定类型 autoload=True, autoload_with=engine ) selectble = sa.sql.select(table.columns).select_from(table) resultList3 = conn.execute(selectble).fetchall() print(resultList3) # [(1, '2017-08-03 01:11:31')]
如果希望把它当成datetime处理,也可以用DateTime类型,SQLAlchemy会自动把字符串转成Python的datetime对象:
sa.Column('datetime', sa.DateTime)
方案二:修改SQLAlchemy的SQLite类型映射
如果你有很多这样的表,可以全局修改SQLAlchemy对SQLite NUMERIC类型的映射,让它默认用String类型:
from sqlalchemy.dialects.sqlite.base import SQLiteDialect SQLiteDialect.colspecs[sa.types.NUMERIC] = sa.types.String
注意:这个修改是全局的,会影响所有使用NUMERIC类型的列,如果你确实有存储数字的NUMERIC列,这个方案可能不适用。
方案三:读取后手动转换(不推荐)
如果不想修改表结构或映射,也可以在读取结果后手动处理(属于临时hack方案):
resultList3 = conn.execute(selectble).fetchall() # 跳过类型转换,直接取原始值 fixed_results = [(row[0], row._parent._row[1]) for row in resultList3] print(fixed_results)
补充说明
SQLite的类型系统是动态弱类型,而SQLAlchemy是强类型ORM,这两者的差异很容易导致这类问题。如果你的datetime数据确实应该是日期时间类型,建议在创建表时直接用DATETIME类型,而不是NUMERIC,这样SQLAlchemy反射时会自动映射为DateTime类型,从根源避免类型转换错误。
内容的提问来源于stack exchange,提问作者egold

