tags:

views:

46

answers:

3

I am running this sort of query:

insert into mytable (id, col1, col2)
values (:ID, :COL1, :COL2)

In Python, a dictionary of this form can be used in conjuction with the query above for parameter substitution:

d = { 'ID' : 0, 'COL1' : 'hi', 'COL2' : 'there' }
cursor.execute(sql_insert, d)

But in the real problem, there are lots of columns and lots of rows. Sometimes the data source that populates the dictionary doesn't have an entry. However, if the dictionary is short, Sqlite will complain that an incorrect number of bindings was supplied, rather than giving me a way to add empty strings or NULLs in the columns which are not populated in this case.

I'm being a lazy, or a bit perfectionist. I could write some code to add any missing fields to the dictionary. I'm just looking for an elegant solution that doesn't require triplicating the list of fields.

I tried overloading the dictionary with a modified dictionary that returns an empty string if the field is missing.

+2  A: 

I haven't checked that this works, but I think it should:

from collections import defaultdict
d = { 'ID' : 0, 'COL1' : 'hi' }
cursor.execute(sql_insert, defaultdict(str, d))

defaultdict is a specialised dictionary where any missing keys generate a new value instead of throwing a KeyError.

Of course this only works if all the values need the same default such as an empty string or None. If you need different defaults then you'll need a dictionary containing the defaults and you can do:

DEFAULTS = { ... whatever ... }
d = { 'ID' : 0, 'COL1' : 'hi' }
cursor.execute(sql_insert, dict(DEFAULTS).update(d))

Note that you must copy DEFAULTS each time so you can update the copy with the actual values.

Duncan
I was definitely looking for defaultdict by didn't know of it. Thanks for the pointer.
jbarlow
A: 

To be honest, I'm not entirely sure what you're asking. It seems you're saying that you want to build an insert statement for a table row where you have some but not all of the field values ready.

You could build your query like this (taking advantage of dict.get returning None for missing keys):

>>> columns = ['id','col1','col2','col_missing']
>>> myData = {'id': 0, 'col1': 'hi', 'col2':'there'}
>>> myQuery = 'insert into mytable (%s) values (%s)' % (",".join(columns),",".join(['%s']*len(columns)))
>>> myArgs = [ myData.get(x) for x in columns ]
>>> myQuery
'insert into mytable (id,col1,col2,col_missing) values (%s,%s,%s,%s)'
>>> myArgs
[0, 'hi', 'there', None]
>>> cursor.execute(myQuery,myArgs)
MattH
That solves one problem (duplicating the column name) in exchange for a worse problem -- opening the door for Sqlite injection attacks via the insert query. http://xkcd.com/327/
jbarlow
The parameter substitution in `cursor.execute` is supposed to be safe. Though apparently it should be '?' instead of '%s' for sqlite3. Where is the injection vector? The column names? They are defined on the schema.
MattH
@jbarlow: Mind telling me what you think the "open door to injection attacks" is in this answer?
MattH
A: 

The DEFAULT constraint specifies a default value to use when doing an INSERT. The value may be NULL, a string constant, a number, or a constant expression enclosed in parentheses

quoted from here.

so use CREATE like this:

CREATE mytable {
    id INTEGER PRIMARY KEY,
    col1 TEXT DEFAULT 'Hello, World!',
    col2 TEXT DEFAULT 'The cake in a lie'
}
Daniel Hill
That doesn't solve it. The issue is that Python's Sqlite library requires all of the bindings too be set for the insert query to be valid -- this is a real a Python problem.
jbarlow