I'm trying to use executemany
in Python to populate some rows.
However, I'm getting:
sqlite3.OperationalError: 3 values for 4 columns
I understand that in general, you use executemany
to stuff raw values into the DB as efficiently as possible, and with that in mind, you generally want the number of values inserted to match the number of columns.
But what if one or more of the columns are "constructed" via SQL, and do not have any corresponding input value?
Small example:
#!/usr/bin/env python2.7
import os
import sys
import sqlite3
from contextlib import closing
DB_FILE = 'test.sqlite'
with closing(sqlite3.connect(DB_FILE)) as db:
# First, some setup...
cursor = db.cursor()
cursor.execute('CREATE TABLE test (id TEXT PRIMARY KEY, a TEXT, b TEXT, c TEXT)')
sql = '''INSERT INTO test (id, a, b, c) VALUES (?, ?, ?, ?)'''
data = []
for i in range(5):
idStr = str(i)
data.append( (idStr, 'a{}'.format(i), 'b{}'.format(i), 'c{}'.format(i)) )
cursor.executemany(sql, data)
# Now, to exercise the problem:
# This does an 'upsert', but the same problem could occur in
# other circumstances.
sql = '''
INSERT OR REPLACE INTO test (id, a, b, c)
VALUES (
?,
(SELECT a,b FROM test WHERE id = ?),
?
)'''
data = []
for i in range(3,7):
idStr = str(i)
data.append( (idStr, idStr, 'c{}'.format(i)) )
cursor.executemany(sql, data)
db.commit()
When you run that, you get the error mentioned at the top (3 values for 4 columns
).
I suppose I could work around this by breaking the SELECT a,b
into separate SELECT a..., SELECT b...
parts, but is there a better way?
The example given is small — in reality, it might be 10 columns that I'll now have to verbosely list with their own (SELECT ...)
clauses.
UPDATE 1 This is not specific to executemany
, as I had originally thought. Even running the plain SQL by hand has the same problem.
UPDATE 2 The answers below suggest using a SELECT
rather than VALUES
, however, this does no work when a new row is being added (as in my "upsert" case), since the SELECT
returns no rows in that case.
Yes! There is a better way! Replace your
VALUES
clause with aSELECT
statement!The query will look like:
Remember to change your
data
list because the parameters are in a different order now.