使用适配器时,sqlite3中十进制和货币的问题

5 投票
2 回答
2141 浏览
提问于 2025-04-16 17:53

我在用sqlite3作为数据库,想保存几个小数值,一个是百分比,另一个是美元金额。不过,我发现当我对超过20,000条记录使用sum(amount)时,结果偏差了好几美元。

我想到了用一个适配器来保存分(也就是美元的百分之一),这样聚合计算应该就能正常工作了。

sqlite3.register_adapter(decimal.Decimal, lambda x:str(x))
sqlite3.register_adapter(decimal.Decimal, lambda x:int(x*100))

不过,这样我就需要两个类,因为我不能用同一个类。尝试去继承Decimal类时遇到了问题,因为它内部又使用了Decimal。没关系,我决定直接复制decimal.py文件,把里面所有的Decimal和decimal都换成Money和money。

$ copy decimal.py money.py
$ sed -e "s/Decimal/Money/g" -e "s/decimal/money/g" -i money.py
$ money.py

所有的单元测试都通过了。我现在试了一下,却出现了“可能不支持的类型”的错误。我调整了一下转换器,使用了一些基本类型,但就是无法正常工作,我也没有更好的解决办法。

我需要一些帮助,下面有个示例。有没有什么想法可以让它正常工作,或者用标准库有更好的解决方案?

import decimal
import money
import sqlite3

sqlite3.register_adapter(decimal.Decimal, lambda x:str(x))
sqlite3.register_converter('decimal', decimal.Decimal)
sqlite3.register_adapter(money.Money, lambda x: int(value*100))
sqlite3.register_converter('intcents', lambda x: money.Money(x)/100)

conn = sqlite3.connect(":memory:",
        detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
cursor = conn.cursor()
cursor.executescript("CREATE TABLE example(rate decimal, amount intcents)")

for x in (1,1,1,2,2,2,3,3,3):
    rate = decimal.Decimal(str(x))/100
    amount = money.Money(str(rate))

    try:
        cursor.execute("INSERT INTO example VALUES(?,?)", (rate, amount))
    except:
        print (rate, amount)
        raise

cursor.execute("""SELECT sum(rate), sum(amount) FROM example""")
print cursor.fetchone()
cursor.execute("""SELECT sum(rate) as "t [decimal]", sum(amount)as "a [intcents]"FROM example""")
print cursor.fetchone()

2 个回答

2

这里有两个简单的解决办法,希望有人能提供更直接的方法:
1. 把数据转换成字符串,然后把这个字符串存到数据库里。
2. 把钱的数值乘以100,然后以整数的形式存储。
无论哪种方法,当你从数据库读取这些值时,都需要再把它们转换回小数格式。

0

问题出在register_adapter这个地方。奇怪的是,它并没有让我遇到NameError(名称错误)。另外一种解决方案需要在每次输入和输出时都进行处理,这样就失去了使用register_adapter和register_converter的意义。按照评论里的语法修正后,它运行得相当不错。

撰写回答