Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem using decimal and money in sqlite3 with adapters

Using sqlite3 as the database, I want to keep a couple of decimal.Decimal values, one a percentage the other a dollar amount. However I saw problems when using sum(amount) on over 20,000 entries. It was several dollars off.

I thought of using an adapter to save the number of cents and then aggregates should work.

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

However, I would now need two classes as I can not use the same class. Trying to subclass Decimal became an issue as it uses Decimal within itself. Fine. I'll just copy decimal.py and replace every occurance of Decimal|decimal with Money|money.

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

All the unit tests work. I try it now and I am getting a "probably unsupported type" error. I change around the converters, use basic types. I just can not get it working right and I don't have a better idea for the problem.

I need some help and have a sample below. Ideas on how to get it working or a better solution using standard libraries?

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()
like image 959
Frustrated Avatar asked Nov 05 '22 21:11

Frustrated


1 Answers

There are two simple solutions, and I hope someone has something more direct:
1. convert to a string and store the string in the DB
2. multiply by 100 and store as an integer for money values
Both of these will require a conversion back to decimal.Decimal when the values are read from the DB.

like image 174
Joe Avatar answered Nov 09 '22 15:11

Joe