如何将 Python 十进制转换为 SQLite 数字?
Posted
技术标签:
【中文标题】如何将 Python 十进制转换为 SQLite 数字?【英文标题】:How to convert Python decimal to SQLite numeric? 【发布时间】:2011-09-13 05:56:34 【问题描述】:我有一个程序可以读取 JSON 格式的财务数据并将其插入 SQLite 数据库。问题是当我将它插入 SQLite 数字列时,它似乎不喜欢 decimal 对象。
我发现了这个问题answered before,但答案已经过时,据我了解,SQLite 现在有一个货币数据类型called numeric。
现在作为一种解决方法,我将十进制值存储为文本,但是否可以将其存储为数字?我是否坚持将小数转换为字符串的开销,反之亦然,以进行数据库插入和财务计算?
【问题讨论】:
使用自己的数据类型并使用BLOB类型? 【参考方案1】: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'>
【讨论】:
此解决方案不再有效。根据 sqlite3 文档,转换器函数始终以bytes
格式调用,因此为了使代码在 Python 3.8 中工作,您必须在 convert_decimal
函数的定义上调用 D(s.decode('utf-8'))
。
@manoelpqueiroz 因为十进制只有 ASCII 字符,所以我会使用 ASCII 编码,即D(s.decode('ascii'))
,因为它快一点。【参考方案2】:
我发现我必须对 unutbu 的 approach 进行一些小调整。对于值为“4.00”的修改示例,它从数据库中返回为“4”。我正在处理商品并且不想将精度硬编码到数据库中(就像我只是乘以除以 100 时所做的那样)。所以我调整了转换函数如下:
def adapt_decimal(d):
return '#'+str(d)
def convert_decimal(s):
return D(s[1:])
这在美学上不是很好,但确实挫败了 sqlite 将字段存储为整数并失去精度的渴望。
【讨论】:
【参考方案3】:column affinity rules for SQLite 说:
如果列的声明类型包含任何字符串 “CHAR”、“CLOB”或“TEXT”,则该列具有 TEXT 亲和性。注意 类型 VARCHAR 包含字符串“CHAR”,因此被分配 TEXT 亲和力。
您可以将列声明为您喜欢的任何类型:
CREATE TABLE a_test(
a_decimal DECTEXT NOT NULL -- will be stored as 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"),))
不知道这是否是处理它的好方法-欢迎 cmets
【讨论】:
【参考方案4】:至少您链接到的页面没有提及currency
数据类型,而decimal(10,5)
example datatype 只是打开了NUMERIC
关联。
如果它是我的数据,我可能会存储整数个货币“单位”——便士,或十分之一便士,或百分之一便士,只要合适——然后使用 比例因子 将整数输入转换为十进制,以在从数据库读取数据时进行计算。整数更难搞砸。
【讨论】:
这不是other question中提到的吗? @William,是的,这远非一个新想法。 :) 请务必为您的应用程序设置足够大的缩放参数。2
可能足以满足小型企业的会计需求,但您可能需要 3
、4
或 5
或更多,具体取决于货币转换或利率计算等。【参考方案5】:
我基本上遵循与其他人相同的方法,但增加了一个。问题是定义适配器和转换器会处理逐行访问十进制列的情况。但是当您发出聚合函数时,例如对所有行的小数列求和,求和是使用实数(即浮点算术)完成的,返回的结果将是带有后续舍入错误的浮点数。
解决方案是创建一个定制的聚合函数,decimal_sum。由于 sqlite3 聚合函数受限于它们可能返回的类型,decimal_sum 将返回十进制和的字符串表示,这将是 Decimal 类型(此代码也兼容 Python 3):
import sqlite3
from decimal import Decimal
# DECTEXT columns will have TEXT affinity:
sqlite3.register_adapter(Decimal, lambda d: str(d))
sqlite3.register_converter("DECTEXT", lambda d: Decimal(d.decode('ascii')))
class DecimalSum:
def __init__(self):
self.sum = None
def step(self, value):
if value is None:
return
v = Decimal(value)
if self.sum is None:
self.sum = v
else:
self.sum += v
def finalize(self):
return None if self.sum is None else str(self.sum)
conn = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
conn.create_aggregate("decimal_sum", 1, DecimalSum)
cursor = conn.cursor()
cursor.execute("""
create table test (
amount DECTEXT not null
)
""")
for _ in range(1000):
cursor.execute("insert into test(amount) values(?)", (Decimal("12.01"),))
conn.commit()
# Uses floating point math:
cursor.execute("select sum(amount) from test")
row = cursor.fetchone()
print('Floating point sum:', row[0], type(row[0]))
# Uses decimal math but returns result as a string
# and so we do a final conversion from string to Decimal:
cursor.execute("select decimal_sum(amount) as `amount [dectext]` from test")
row = cursor.fetchone()
print('Decimal sum:', row[0], type(row[0]))
cursor.close()
conn.close()
打印:
Floating point sum: 12010.000000000178 <class 'float'>
Decimal sum: 12010.00 <class 'decimal.Decimal'>
【讨论】:
以上是关于如何将 Python 十进制转换为 SQLite 数字?的主要内容,如果未能解决你的问题,请参考以下文章