"Frank Millman"  wrote in message news:n4ei3l$b98$1...@ger.gmane.org...

I need to store Decimal objects in a sqlite3 database, using Python 3.4 on Windows 7.

I followed the instructions here -


http://stackoverflow.com/questions/6319409/how-to-convert-python-decimal-to-sqlite-numeric

It seemed to work well, but then I hit a problem.

[...]

I have found a workaround for my problem, but first I needed to understand what was going on more clearly. This is what I have figured out.

1. The solution in the SO article is a bit of sleight of hand, though very effective. It does not create a Decimal type in sqlite3. It simply provides a way of converting Decimal objects to strings when you pass them into the database, and converting them back to Decimal types when you read them back.

2. This works if you only use sqlite3 as a storage mechanism, and use Python to perform any arithmetic required. It fails when you try to use sqlite3 to perform arithmetic, as it uses floating point internally and suffers from the same problem that Python does when trying to mix floating point and precise decimal representation.

3. Normally I do use Python to perform the arithmetic, but in this situation I wanted to do the following -

   UPDATE table SET balance = balance + ? WHERE date > ?

It would be very inefficient to read every row into Python, perform the addition, and write it back again.

4. The Python sqlite3 module allows you to create a user-defined function that you can use from within SQL statements. I realised I could use this to get the best of both worlds. I wrote the following function -

   def aggregate(curr_value, aggr_value):
       return '#{}'.format(D(curr_value[1:]) + D(aggr_value[1:]))

and added this to the connection -

   conn.create_function('aggregate', 2, aggregate)

I could then rewrite my statement as -

   UPDATE table SET balance = aggregate(balance, ?) WHERE date > ?

5. The reason for the '#' in the above function is that sqlite3 passes the current value of 'balance' into my function, and it has a bad habit of trying to second-guess the data-type to use. Even though I store it as a string, it passes in an integer or float. Prefixing it with a '#' forces it to remain as a string.

My adapters therefore now look like this -

   # Decimal adapter (store Decimal in database as str)
   sqlite3.register_adapter(D, lambda d:'#'+str(d))

   # Decimal converter (convert back to Decimal on return)
   sqlite3.register_converter('DEC', lambda s: D(s.decode('utf-8')[1:]))

6. Putting it all together, I can now run my test program -

   while True:
       print(cur.execute("SELECT bal FROM fmtemp").fetchone()[0])
cur.execute("UPDATE fmtemp SET bal = aggregate(bal, ?)", (D('123.45'),))
       q = input()
       if q == 'q':
           break

and it runs up to 123450.00 without misbehaving.

Hope this is of interest.

Frank


--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to