117

I have the following Python code:

cursor.execute("INSERT INTO table VALUES var1, var2, var3,")

where var1 is an integer, var2 and var3 are strings.

How can I write the variable names without Python including them as part of the query text?

snakecharmerb
  • 36,887
  • 10
  • 71
  • 115
user111606
  • 1,173
  • 2
  • 8
  • 4

5 Answers5

139
cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))

Note that the parameters are passed as a tuple.

The database API does proper escaping and quoting of variables. Be careful not to use the string formatting operator (%), because

  1. it does not do any escaping or quoting.
  2. it is prone to Uncontrolled string format attacks e.g. SQL injection.
Ayman Hourieh
  • 122,950
  • 22
  • 139
  • 114
  • Interesting, why does it work with the vars separately instead of in an array (var1,var2,var3)? – Andomar May 23 '09 at 20:31
  • According to the DB API specs, it looks like it can be either way: http://www.python.org/dev/peps/pep-0249/ – Ayman Hourieh May 23 '09 at 20:57
  • 9
    @thekashyap Read again carefully. What's insecure is using the string formatting operator `%`. In fact, I say so in the answer. – Ayman Hourieh Feb 10 '14 at 21:21
  • my bad.. I imagined a `%` instead of `,` beteween the string and variables.. can't undo my vote-down due to various reasons.. I personally would like to see words like insecure / attack etc mentioned in the description where you say don't use `%`.. – Kashyap Feb 10 '14 at 22:28
  • Coud you maybe update your answer? I used ? instead of %s because the latter threw an sqlite3 error –  Nov 13 '15 at 11:50
  • Python converts values in tuple to a data type that MySQL understands and adds the required quotes. – ns94 Apr 03 '17 at 07:32
  • 3
    Downvoted because answer says not to use `%` but uses it three times. More explanation would be great. – eric Dec 08 '19 at 04:09
  • 6
    @eric the answer says do not use the `%` *operator* to format the string. Those `%` in the string are being used by `cursor.execute` directly, and since it knows it's generating SQL it can do more to protect you. – Mark Ransom Aug 07 '20 at 16:21
  • https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-execute.html https://www.athenic.net/posts/2017/Jan/21/preventing-sql-injection-in-python/ – ali reza Nov 28 '20 at 08:55
84

Different implementations of the Python DB-API are allowed to use different placeholders, so you'll need to find out which one you're using -- it could be (e.g. with MySQLdb):

cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))

or (e.g. with sqlite3 from the Python standard library):

cursor.execute("INSERT INTO table VALUES (?, ?, ?)", (var1, var2, var3))

or others yet (after VALUES you could have (:1, :2, :3) , or "named styles" (:fee, :fie, :fo) or (%(fee)s, %(fie)s, %(fo)s) where you pass a dict instead of a map as the second argument to execute). Check the paramstyle string constant in the DB API module you're using, and look for paramstyle at http://www.python.org/dev/peps/pep-0249/ to see what all the parameter-passing styles are!

Alex Martelli
  • 811,175
  • 162
  • 1,198
  • 1,373
61

Many ways. DON'T use the most obvious one (%s with %) in real code, it's open to attacks.

Here copy-paste'd from pydoc of sqlite3:

# Never do this -- insecure!
symbol = 'RHAT'
c.execute("SELECT * FROM stocks WHERE symbol = '%s'" % symbol)

# Do this instead
t = ('RHAT',)
c.execute('SELECT * FROM stocks WHERE symbol=?', t)
print c.fetchone()

# Larger example that inserts many records at a time
purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
             ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
             ('2006-04-06', 'SELL', 'IBM', 500, 53.00),
            ]
c.executemany('INSERT INTO stocks VALUES (?,?,?,?,?)', purchases)

More examples if you need:

# Multiple values single statement/execution
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', ('RHAT', 'MSO'))
print c.fetchall()
c.execute('SELECT * FROM stocks WHERE symbol IN (?, ?)', ('RHAT', 'MSO'))
print c.fetchall()
# This also works, though ones above are better as a habit as it's inline with syntax of executemany().. but your choice.
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', 'RHAT', 'MSO')
print c.fetchall()
# Insert a single item
c.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', ('2006-03-28', 'BUY', 'IBM', 1000, 45.00))
Stevoisiak
  • 20,148
  • 23
  • 110
  • 201
Kashyap
  • 13,836
  • 12
  • 58
  • 96
  • 8
    Some of the DB-API implementations actually use %s for their variables -- most notably psycopg2 for PostgreSQL. This is not to be confused (though it easily is) with using %s with the % operator for string replacement. I would be really nice if, for portability, we could just have a defined standard way of specifying SQL parameters for DB-API. – ThatAintWorking Nov 24 '14 at 18:03
27

http://www.amk.ca/python/writing/DB-API.html

Be careful when you simply append values of variables to your statements: Imagine a user naming himself ';DROP TABLE Users;' -- That's why you need to use SQL escaping, which Python provides for you when you use cursor.execute in a decent manner. Example in the URL is:

cursor.execute("insert into Attendees values (?, ?, ?)", (name, seminar, paid))
mkrieger1
  • 14,486
  • 4
  • 43
  • 54
Numlock
  • 291
  • 2
  • 3
  • 14
    Actually, it is not SQL escaping. It's variable binding, which is far simpler and more direct. The values are bound into the SQL statement after parsing, making it immune to any injection attack. – S.Lott May 23 '09 at 20:34
  • 1
    well, whether it's SQL escaping or variable binding depends on how good or bad your database server / DB-API driver is. I've seen some real-world, widely-deployed production databases that have their DB-API driver just do escaping, rather than keeping data and code out-of-band on the wire. Needless to say, I don't have very much respect for those so-called "databases". – Charles Duffy Mar 12 '20 at 18:20
4

The syntax for providing a single value can be confusing for inexperienced Python users.

Given the query

INSERT INTO mytable (fruit) VALUES (%s)

Generally*, the value passed to cursor.execute must wrapped in a sequence such as a tuple or list even though the value itself is a singleton, so we must provide a single element tuple, like this: (value,).

cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple',))

Passing a single string

cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple'))

will result in an error which varies by the DB-API connector, for example

  • psycopg2:

    TypeError: not all arguments converted during string formatting

  • sqlite3

    sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 5 supplied

  • mysql.connector

    mysql.connector.errors.ProgrammingError: 1064 (42000): You have an error in your SQL syntax;


* The pymysql connector handles a single string parameter without erroring. However it's better to wrap the string in a tuple even if it's a single because

  • you won't need to change the code if you switch connector package
  • you keep a consistent mental model of the query parameters being a sequence of objects rather than a single object.
snakecharmerb
  • 36,887
  • 10
  • 71
  • 115