我有一个程序,它以JSON格式读取财务数据并将其插入到SQLite数据库中。问题是当我将它插入SQLite数字列时,它似乎不喜欢decimal对象。
我发现了这个问题answered before,但答案已经过时,据我所知,SQLite现在有货币数据类型called numeric。
现在作为一种解决方法我将十进制值存储为文本,但是可以将其存储为数字吗?我是否坚持将小数转换为字符串的开销,反之亦然,以便进行数据库插入和财务计算?
答案 0 :(得分:21)
sqlite3
允许您注册适配器(插入时透明地将Decimals
转换为TEXT
)和转换器(透明地将TEXT
转换为Decimals
提取时。)
以下是来自the docs的示例代码的轻微修改版本:
import sqlite3
import decimal
D=decimal.Decimal
def adapt_decimal(d):
return str(d)
def convert_decimal(s):
return D(s)
# Register the adapter
sqlite3.register_adapter(D, adapt_decimal)
# Register the converter
sqlite3.register_converter("decimal", convert_decimal)
d = D('4.12')
con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)
cur = con.cursor()
cur.execute("create table test(d decimal)")
cur.execute("insert into test(d) values (?)", (d,))
cur.execute("select d from test")
data=cur.fetchone()[0]
print(data)
print(type(data))
cur.close()
con.close()
产量
4.12
<class 'decimal.Decimal'>
答案 1 :(得分:1)
至少您链接的网页未提及currency
数据类型,而decimal(10,5)
example datatype只是打开了NUMERIC
亲和关系。
如果是我的数据,我可能存储整数个货币“单位” - 便士,或一分钱的十分之一,或一分钱的百分之一,无论什么是合适的 - 然后使用缩放因子将整数输入转换为十进制等效值,以便在从数据库中读取数据时进行计算。搞乱整数更难。
答案 2 :(得分:1)
我发现我必须对unutbu的方法进行一些小调整。使用值为“4.00”的修改示例,它以“4”的形式从数据库中返回。我正在处理商品,并且不想将精度硬编码到数据库中(就像我只是乘以并除以100一样)。所以我调整了转换函数如下:
def adapt_decimal(d):
return '#'+str(d)
def convert_decimal(s):
return D(s[1:])
这在美学上并不是很好,但是它确实击败了sqlite渴望将该字段存储为整数并且无法跟踪精度。
答案 3 :(得分:0)
http://www.sqlite.org/datatype3.html#affinity
您可以将列声明为您喜欢的任何类型:
CREATE TABLE a_test( a_decimal DECTEXT NOT NULL / *将存储为TEXT * / );
def adapt_decimal(d):
return str(d)
def convert_decimal(s):
return decimal.Decimal(s)
# Register the adapter
sqlite3.register_adapter(decimal.Decimal, adapt_decimal)
# Register the converter
sqlite3.register_converter("DECTEXT", convert_decimal)
con = sqlite3.connect("test.s3db", detect_types=sqlite3.PARSE_DECLTYPES)
C1 = con.cursor()
C1.execute("INSERT INTO a_test VALUES(?)", (decimal.Decimal("102.20"),))
不知道这是否是处理它的好方法 - 欢迎评论
答案 4 :(得分:0)
我基本上遵循与其他人相同的方法,但增加了一个。问题在于定义适配器和转换器会处理您逐行访问十进制列的情况。但是当您发出聚合函数时,例如对所有行的小数列求和,求和是使用实数(即浮点运算)完成的,返回的结果将是浮点数。在下面的示例中,我知道我存储的十进制值带有两位小数(代表美国货币),因此我希望返回的最终答案四舍五入到两位小数。我可以在检索浮点总和后执行此操作。但这很简单:
decimal2
类型指定一个额外的转换器,假定精度为两位小数detect_types
值 sqlite3.PARSE_COLNAMES
连接到数据库import sqlite3
from decimal import Decimal, ROUND_HALF_UP
sqlite3.register_adapter(Decimal, lambda d: str(d).encode('ascii'))
sqlite3.register_converter("decimal", lambda d: Decimal(d.decode('ascii')))
sqlite3.register_converter("decimal2", lambda d: Decimal(d.decode('ascii')).quantize(Decimal('.01'), rounding=ROUND_HALF_UP))
conn = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
cursor = conn.cursor()
cursor.execute("""
create table test (
amount decimal not null
)
""")
for _ in range(1000):
cursor.execute("insert into test(amount) values(?)", ("12.01",))
conn.commit()
cursor.execute("select sum(amount) from test")
row = cursor.fetchone()
print(row[0])
# force conversion from real (floating point) to Decimal rounded to two places
cursor.execute("select sum(amount) as `amount [decimal2]` from test")
row = cursor.fetchone()
print(row[0])
cursor.close()
conn.close()
打印:
12010.000000000178
12010.00
重要提示
这个解决方案并不完美。如果您聚合足够多的行,则浮点近似值可能会累积,以至于在将总和转换回十进制时四舍五入到两个位置不足以获得正确答案。例如,如果我们插入并求和 10,000,000 行:
for _ in range(10000000):
cursor.execute("insert into test(amount) values(?)", ("12.01",))
conn.commit()
那么我们将获得的总和将是 120100000.02
而不是所希望的 120100000.01
。因此,要绝对确保聚合并精确将正确结果放在正确数量的位置上,我们必须单独读取和求和每一行:
cursor.execute("select amount from test")
sum = Decimal('0.00')
for row in iter(cursor.fetchone, None):
sum += row[0]
print(sum)
打印:
120100000.00
但对于许多情况,我所概述的方法应该足够了,而且肯定比完全省略舍入要好。
例如。 AVG
函数不太可能以与 SUM
函数相同的程度呈现舍入问题,并且它的值是您可能想要舍入的值。在以下示例中,我们在 .01
和 7777.77
之间插入 10,000,000 个随机值,并首先使用 AVG
聚合函数计算四舍五入到两个位置的平均值,然后将各行相加并除以行数并将这个商四舍五入到两个位置:
for _ in range(10000000):
cursor.execute("insert into test(amount) values(?)", (Decimal(randint(1, 777777)) / 100,))
conn.commit()
cursor.execute("select avg(amount) as `amount [decimal2]` from test")
row = cursor.fetchone()
print(row[0])
cursor.execute("select amount from test")
sum = Decimal('0.00')
cnt = 0
for row in iter(cursor.fetchone, None):
sum += row[0]
cnt += 1
print((sum / cnt).quantize(Decimal('.01'), rounding=ROUND_HALF_UP))
打印:
3889.54
3889.54