- ORM
- 数据库
- 后端
【免费下载链接】sqlmodel
SQL databases in Python, designed for simplicity, compatibility, and robustness.
本指南以 SQLModel 官方文档 Decimal Numbers 为核心,系统讲解如何在 SQLModel 模型中使用 Python 标准库decimal.Decimal类型来存储货币、价格、账户余额等对精度有严格要求的财务数据。读完本文,你将掌握Field()中max_digits与decimal_places参数的精确配置规则、合法的数值范围判断方法,以及 SQLModel 底层如何将Decimal映射为 SQLAlchemy 的DECIMAL/Numeric数据库列类型。
为什么财务数据需要 Decimal
在二进制计算机中,浮点数(float)的存储方式决定了它无法精确表达所有十进制小数。以最常见的例子来说,在 Python 中执行1.1 + 2.2,直觉上应该得到3.3,实际却得到:
>>> 1.1 + 2.2 3.3000000000000003这是因为1.1和2.2在二进制中都是无限循环小数,只能近似存储,累加后误差便暴露出来。Python 提供了decimal标准库模块和Decimal类型来解决这类问题,它可以以十进制的形式严格保存数值,从而保证运算结果的确定性。
数据库在底层同样以二进制存储数据,因此也存在相同的问题,这也正是主流数据库都提供专用decimal类型的原因。对多数场景(如统计视频播放量、游戏角色血条)来说,浮点误差通常无关紧要;但对于货币、价格、账户余额这类涉及金钱与财务计算的业务,四舍五入误差是绝对不能接受的,此时就必须使用 Decimal。
SQLModel 中 Decimal 的类型基础
Pydantic 对Decimal类型有专门支持。当你在 SQLModel 模型字段上使用Decimal时,可以通过Field()函数指定该数值允许的总位数(digits)和小数位数(decimal places):
max_digits:数值允许的最大总位数,同时包含整数部分和小数部分;decimal_places:小数点右侧允许的小数位数。
这两个参数一方面由 Pydantic 在校验阶段(例如配合 FastAPI 做请求体校验时)强制执行,另一方面会被 SQLModel 原样传递给数据库列定义,让数据库层面的精度约束与模型层面的校验保持一致。
从源码看,SQLModel 的Field()签名完整接收max_digits与decimal_places两个参数(见 sqlmodel/main.py),并在构造字段信息时将其原样转发给底层 Pydantic 元数据(见 sqlmodel/main.py)。在内部兼容层中,SQLModel 通过FakeMetadata类持有这两个属性供后续类型映射读取(见 sqlmodel/_compat.py)。
底层数据库类型映射
SQLModel 在将模型字段转换为数据库列时,会通过get_sqlalchemy_type()函数完成 Python 类型到 SQLAlchemy 类型的映射。当字段类型是Decimal时,映射逻辑如下(见 sqlmodel/main.py):
if issubclass(type_, Decimal): return Numeric( precision=getattr(metadata, "max_digits", None), scale=getattr(metadata, "decimal_places", None), )也就是说,SQLModel 使用 SQLAlchemy 的DECIMAL类型(即Numeric)来表示 Decimal 字段,并将max_digits映射为 SQL 层面的precision(精度),将decimal_places映射为scale(小数位)。这意味着你在 Python 模型中声明的精度约束,会直接体现在数据库表结构中,形成端到端的精度保证。
在模型中使用 Decimal 字段
假设数据库中的每个 hero(英雄)都有一笔钱,我们可以将该字段声明为Decimal,并用Field()参数配置最大位数与小数位。完整示例代码如下(见 docs_src/advanced/decimal/tutorial001_py310.py):
from decimal import Decimal from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str = Field(index=True) secret_name: str age: int | None = Field(default=None, index=True) money: Decimal = Field(default=0, max_digits=5, decimal_places=3) sqlite_file_name = "database.db" sqlite_url = f"sqlite:///{sqlite_file_name}" engine = create_engine(sqlite_url, echo=True) def create_db_and_tables(): SQLModel.metadata.create_all(engine) def create_heroes(): hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson", money=1.1) hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador", money=0.001) hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48, money=2.2) with Session(engine) as session: session.add(hero_1) session.add(hero_2) session.add(hero_3) session.commit()其中money: Decimal = Field(default=0, max_digits=5, decimal_places=3)的含义是:
max_digits=5:money字段最多允许5 位数字,这 5 位同时包括整数部分(小数点左侧)和小数部分(小数点右侧);decimal_places=3:小数点右侧最多3 位小数。
因此,该字段的整数部分最多只能是5 - 3 = 2位,即数值范围为0到99.999之间(考虑符号则为-99.999到99.999)。
合法的数值示例
✅ 以下数值对money字段都是合法的:
12.345—— 5 位数字,其中整数 2 位、小数 3 位;12.3—— 不足 3 位小数时按 3 位小数存储(即12.300);12—— 无小数部分,整数 2 位;1.2—— 整数 1 位、小数 1 位;0.123—— 整数 0 位、小数 3 位;0—— 全零值。
非法的数值示例
🚫 以下数值对money字段都是非法的:
1.2345—— 小数位数超过 3 位(4 位小数);123.234—— 总位数超过 5 位(整数部分 3 位 + 小数部分 3 位);123—— 虽然没有任何小数位,但字段仍为小数保留了 3 位,因此整数部分只能使用max_digits - decimal_places = 2位,而123有 3 位整数数字,超出限制。
提示:请务必根据自己应用的实际业务需求调整位数和小数位配置。例如货币场景通常需要
decimal_places=2(分),而科学计算或高精度统计可能需要更多小数位。
创建带 Decimal 字段的模型数据
创建模型实例时,你完全可以直接传入普通的float数字,Pydantic 会自动将其转换为Decimal类型,SQLModel 再通过 SQLAlchemy 以Decimal类型存入数据库。例如上面的示例代码中:
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson", money=1.1) hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador", money=0.001) hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48, money=2.2)这里传入的1.1、0.001、2.2都是 Pythonfloat,经过 Pydantic 校验后被规范化为Decimal('1.100')、Decimal('0.001')、Decimal('2.200')存储。注意1.1被自动补零为1.100,以满足decimal_places=3的精度约定。
查询 Decimal 数据并验证精度
写入数据之后,再读取 Decimal 字段并参与运算,即可验证它确实规避了浮点数的舍入误差:
def select_heroes(): with Session(engine) as session: statement = select(Hero).where(Hero.name == "Deadpond") results = session.exec(statement) hero_1 = results.one() print("Hero 1:", hero_1) statement = select(Hero).where(Hero.name == "Rusty-Man") results = session.exec(statement) hero_2 = results.one() print("Hero 2:", hero_2) total_money = hero_1.money + hero_2.money print(f"Total money: {total_money}") def main(): create_db_and_tables() create_heroes() select_heroes() if __name__ == "__main__": main()注意这里的hero_1.money + hero_2.money是两个Decimal对象直接相加,其结果依然是Decimal,因此不会产生浮点累加误差。
运行程序(示例中使用了 uv 作为包管理器,若你的环境不同可替换为python app.py):
$ uv run python app.py // 部分样板输出已省略 // The type of money is Decimal('1.100') Hero 1: id=1 secret_name='Dive Wilson' age=None name='Deadpond' money=Decimal('1.100') // 更多输出已省略 // The type of money is Decimal('1.100') Hero 2: id=3 secret_name='Tommy Sharp' age=48 name='Rusty-Man' money=Decimal('2.200') // 没有舍入误差,就是 3.3! Total money: 3.300可以看到,最终输出是精确的3.300,而不是浮点运算时出现的3.3000000000000003。这正是 Decimal 类型在财务计算中的核心价值。
测试用例佐证
仓库中的测试文件 tests/test_advanced/test_decimal/test_tutorial001.py 对上述行为做了完整验证。测试用内存型 SQLite 引擎(sqlite://)替换示例中的文件数据库,然后运行示例的main(),并断言:
Hero 1的money等于Decimal("1.100");Hero 2的money等于Decimal("2.200");- 两笔钱相加的结果打印为
Total money: 3.300。
这从自动化测试层面确认了:从 Python 类型转换、数据库存取到算术运算,Decimal 的精度在整个链路上都得到了保持。
重要警告:SQLite 不支持 Decimal
虽然 Decimal 类型在 Python 侧得到完整支持,但并非所有数据库都支持 Decimal 类型。需要特别注意的是:
SQLite 不支持 Decimal。在 SQLite 中,Decimal 会被转换为它支持的浮点型
NUMERIC类型,这意味着精度保证在 SQLite 下无法落实。
这一点在示例代码中也能得到印证——示例默认使用sqlite:///database.db作为数据库引擎,因此实际的精度行为取决于底层数据库。好消息是,绝大多数其他 SQL 数据库(如 PostgreSQL、MySQL 等)都原生支持 Decimal 类型,在这些数据库上,SQLModel 会以真正的DECIMAL(precision, scale)列来存储,精度约束由数据库引擎强制执行。
因此,如果你的应用涉及金额等财务数据,建议在生产环境选择支持 Decimal 的数据库,并在部署前用真实数据库验证精度行为。
实战要点总结
- 何时使用 Decimal:涉及货币、价格、账户余额、税率等对舍入误差敏感的财务场景,一律使用
Decimal;普通计数、度量等场景使用float即可。 - 字段声明方式:
money: Decimal = Field(default=0, max_digits=5, decimal_places=3),其中max_digits包含整数与小数全部位数,整数部分上限为max_digits - decimal_places。 - 写入无需手动转换:创建模型时可以传
float,Pydantic 自动转换为Decimal并补足小数位。 - 运算保持精确:
Decimal之间的加、减、乘、除不会引入浮点舍入误差,适合直接用于财务计算。 - 底层映射:SQLModel 将
Decimal映射为 SQLAlchemy 的Numeric/DECIMAL类型,max_digits对应precision、decimal_places对应scale(见 sqlmodel/main.py)。 - 数据库选型:SQLite 会把 Decimal 降级为浮点
NUMERIC,精度无法保证;生产环境请选用支持 Decimal 的 SQL 数据库。 - 自动化验证:可以参考 tests/test_advanced/test_decimal/test_tutorial001.py 的写法,为财务字段编写精度断言测试,防止回归。
- ORM
- 数据库
- 后端
【免费下载链接】sqlmodel
SQL databases in Python, designed for simplicity, compatibility, and robustness.
相关推荐
数据处理与存储:从文件到数据库的完整流程
数据处理与存储:从文件到数据库的完整流程 本文全面探讨了数据处理与存储的完整技术流程,涵盖了从序列号生成算法、MySQL关系型数据库操作、Redis非关系型数据
SQLModel事务处理最佳实践:确保数据一致性的完整方案
SQLModel事务处理最佳实践:确保数据一致性的完整方案 SQLModel作为Python中强大的ORM工具,提供了完善的事务处理机制来保证数据库操作的数据一
ORM数据库后端OpenPAI 数据管理完全指南:从存储配置到任务使用
OpenPAI 数据管理完全指南:从存储配置到任务使用 前言 在OpenPAI深度学习平台中,高效的数据管理是机器学习工作流的关键环节。本文将全面介绍如何在Op
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考