Using a WHERE ___ IN ___ statement - python

I'm trying to figure out how to properly use a WHERE _ IN _ statement
Definition:
c.execute('''CREATE TABLE IF NOT EXISTS tab (
_id integer PRIMARY KEY AUTOINCREMENT,
obj text NOT NULL
) ;''')
I'm trying to do something like this:
list_of_vars=['foo','bar']
statement="SELECT * FROM tab WHERE obj IN (?)"
c.execute(statement,"'"+"','".join(list_of_vars)+"'")
Alternatively, I've also tried this, which directly evaluates to the above
statement="SELECT * FROM tab WHERE obj IN (?)"
c.execute(statement,"'foo','bar'")
The error I am getting is:
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 9 supplied
This is giving me an error. When I do it this way, it works, but this is not recommended as it is vulnerable to a SQL injection attack.
statement="SELECT * FROM tab WHERE obj IN ("+"'"+"','".join(statement)+"'"+")

You need to create enough parameters to match your list of vars:
statement = "SELECT * FROM tab WHERE obj IN ({0})".format(', '.join(['?'] * len(list_of_vars)))
c.execute(statement, list_of_vars)
Note that you pass in list_of_vars as the parameter values list. Using the ', '.join() we generate a string of ? characters separated by commas, then use .format() to insert that into the statement.
For a long list of variables, it may be more efficient to use a temporary table to hold those values, then use a JOIN against the temporary table rather than an IN clause with bind parameters.

FYI, pymysql with MySQL user.
query ="SELECT * FROM tab WHERE obj IN %s"
cursor.execute(query, (['foo','bar'],))
same as
cursor.execute(query, (list_of_var,))
I'm not sure about sqlite3,
this may work,
query ="SELECT * FROM tab WHERE obj IN ?"
cursor.execute(query, (['foo','bar'],))
or
cursor.execute(query, (list_of_vars,))

Related

Sqlite3 / Python -- increment column by 1 using '?' and UPDATE, getting syntax error [duplicate]

I want to dynamically choose what table to use in a SQL query, but I just keep getting error however I am trying to format this. Also tried %s instead of ?.
Any suggestions?
group_food = (group, food)
group_food_new = (group, food, 1)
with con:
cur = con.cursor()
tmp = cur.execute("SELECT COUNT(Name) FROM (?) WHERE Name=?", group_food)
if tmp == 0:
cur.execute("INSERT INTO ? VALUES(?, ?)", group_food_new)
else:
times_before = cur.execute("SELECT Times FROM ? WHERE Name=?", group_food)
group_food_update = (group, (times_before +1), food)
cur.execute("UPDATE ? SET Times=? WHERE Name=?", group_food_update)
You cannot use SQL parameters to be placeholders in SQL objects; one of the reasons for using a SQL parameters is to escape the value such that the database can never mistake the contents for a database object.
You'll have to interpolate the database objects separately; escape your identifiers by doubling any " double quote parameters and use
cur.execute('SELECT COUNT(Name) FROM "{}" WHERE Name=?'.format(group.replace('"', '""')), (food,))
and
cur.execute('INSERT INTO "{}" VALUES(?, ?)'.format(group.replace('"', '""')), (food, 1))
and
cur.execute('UPDATE "{}" SET Times=? WHERE Name=?'.format(group.replace('"', '""')),
(times_before + 1, food))
The ".." double quotes are there to properly demark an identifier, even if that identifier is also a valid keyword; any existing " characters in the name must be doubled; this also helps de-fuse SQL injection attempts.
However, if your object names are user-sourced, you'll have to do your own (stringent) validation on the object names to prevent SQL injection attacks here. Always validate them against existing objects in that case.
You should really consider using a project like SQLAlchemy to generate your SQL instead; it can take care of validating object names and rigorously protect you from SQL injection risks. It can load your table definitions up front so it'll know what names are legal:
from sqlalchemy import create_engine, func, select, MetaData
engine = create_engine('sqlite:////path/to/database')
meta = MetaData()
meta.reflect(bind=engine)
conn = engine.connect()
group_table = meta.tables[group] # can only find existing tables
count_statement = select([func.count(group_table.c.Name)], group_table.c.Name == food)
count, = conn.execute(count_statement).fetchone()
if count:
# etc.
What are the values of group and food? The guidelines say to make the question as such others can benefit from it, for that to be the case here we need the values of group and food.
It seems you use the Python String formatter instead of SQL parameters for table names even though https://docs.python.org/3/library/sqlite3.html#module-sqlite3 says using the String formatter in unsafe.
# 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)
Using %s as a placeholder then putting % outside the string with the variables in Python2 has been Replaced in Python3 with .format() function with the variables as arguments.
I've found building the SQL query up as a text string and then passing this string into the c.execute() function works.
querySelect = "SELECT * FROM " + str(your_table_variable)
queryWhere = " WHERE " + str(variableName) + " = " str(variableValue)
query = querySelect + queryWhere
c.execute(query)
I don't know the security situation around it though (re injection) and I'm sure there are probably better ways of doing this.

sqlite3 error when attempting to insert info, incorrect number of bindings supplied

I am trying to create a table in sqlite with some data in a .txt file. I am using python, and this is my code:
import sqlite3
conn = sqlite3.connect('TEST_Inventory.sqlite')
cur = conn.cursor()
cur.execute('''
DROP TABLE IF EXISTS InventoryData''')
cur.execute('''
CREATE TABLE `InventoryData` (
`INV_ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
`Inventory_Name` TEXT)''')
fname = ('NameINV.txt')
fh = open(fname)
for line in fh:
name = line.split()
print name
cur.execute('''INSERT INTO InventoryData (Inventory_Name)
VALUES ( '?' )''', (name,))
cur.commit()
However, every time I try to run this code I get this error:
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 0, and there are 1 supplied.
What exactly is going on here?
Sql statements can be constructed in 2 ways.
By concatenating string (unsafe, easy for sql injection attacks)
ex: sql_string = 'INSERT INTO TABLE_1 (COL1, COL2) VALUES(\'' + value1 + '\',\'' + value2 + '\')'
By using placeholders and bind variables (recommended)
ex: sql_string = 'INSERT INTO TABLE_1(COL1,COL2) VALUES (?,?)' and supply bind variables as (value1,value2)
When you use bind variables, there is no need to quote them (to identify them as string/text data). Bind variables will be substituted at database, during execution of statement.
To answer precisely, when you use single-quote around placeholder '?', it will be treated as a text and program doesn't know where to bind the data you supplied while executing query. Just use ? without quotes, so that, parser finds placeholders to substitute with data and code will execute.
cur.execute('''INSERT INTO InventoryData (Inventory_Name)
VALUES ( ? )''', (name,))
Don't quote the placeholder; quoting is taken care of by the database driver when replacing the placeholder with the value:
cur.execute('''INSERT INTO InventoryData (Inventory_Name)
VALUES ( ? )''', (name,))

sqlite3 Operational Error Syntax Error [duplicate]

I want to dynamically choose what table to use in a SQL query, but I just keep getting error however I am trying to format this. Also tried %s instead of ?.
Any suggestions?
group_food = (group, food)
group_food_new = (group, food, 1)
with con:
cur = con.cursor()
tmp = cur.execute("SELECT COUNT(Name) FROM (?) WHERE Name=?", group_food)
if tmp == 0:
cur.execute("INSERT INTO ? VALUES(?, ?)", group_food_new)
else:
times_before = cur.execute("SELECT Times FROM ? WHERE Name=?", group_food)
group_food_update = (group, (times_before +1), food)
cur.execute("UPDATE ? SET Times=? WHERE Name=?", group_food_update)
You cannot use SQL parameters to be placeholders in SQL objects; one of the reasons for using a SQL parameters is to escape the value such that the database can never mistake the contents for a database object.
You'll have to interpolate the database objects separately; escape your identifiers by doubling any " double quote parameters and use
cur.execute('SELECT COUNT(Name) FROM "{}" WHERE Name=?'.format(group.replace('"', '""')), (food,))
and
cur.execute('INSERT INTO "{}" VALUES(?, ?)'.format(group.replace('"', '""')), (food, 1))
and
cur.execute('UPDATE "{}" SET Times=? WHERE Name=?'.format(group.replace('"', '""')),
(times_before + 1, food))
The ".." double quotes are there to properly demark an identifier, even if that identifier is also a valid keyword; any existing " characters in the name must be doubled; this also helps de-fuse SQL injection attempts.
However, if your object names are user-sourced, you'll have to do your own (stringent) validation on the object names to prevent SQL injection attacks here. Always validate them against existing objects in that case.
You should really consider using a project like SQLAlchemy to generate your SQL instead; it can take care of validating object names and rigorously protect you from SQL injection risks. It can load your table definitions up front so it'll know what names are legal:
from sqlalchemy import create_engine, func, select, MetaData
engine = create_engine('sqlite:////path/to/database')
meta = MetaData()
meta.reflect(bind=engine)
conn = engine.connect()
group_table = meta.tables[group] # can only find existing tables
count_statement = select([func.count(group_table.c.Name)], group_table.c.Name == food)
count, = conn.execute(count_statement).fetchone()
if count:
# etc.
What are the values of group and food? The guidelines say to make the question as such others can benefit from it, for that to be the case here we need the values of group and food.
It seems you use the Python String formatter instead of SQL parameters for table names even though https://docs.python.org/3/library/sqlite3.html#module-sqlite3 says using the String formatter in unsafe.
# 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)
Using %s as a placeholder then putting % outside the string with the variables in Python2 has been Replaced in Python3 with .format() function with the variables as arguments.
I've found building the SQL query up as a text string and then passing this string into the c.execute() function works.
querySelect = "SELECT * FROM " + str(your_table_variable)
queryWhere = " WHERE " + str(variableName) + " = " str(variableValue)
query = querySelect + queryWhere
c.execute(query)
I don't know the security situation around it though (re injection) and I'm sure there are probably better ways of doing this.

Python Sqlite3: INSERT INTO table VALUE(dictionary goes here)

I would like to use a dictionary to insert values into a table, how would I do this?
import sqlite3
db = sqlite3.connect('local.db')
cur = db.cursor()
cur.execute('DROP TABLE IF EXISTS Media')
cur.execute('''CREATE TABLE IF NOT EXISTS Media(
id INTEGER PRIMARY KEY, title TEXT,
type TEXT, genre TEXT,
onchapter INTEGER, chapters INTEGER,
status TEXT
)''')
values = {'title':'jack', 'type':None, 'genre':'Action', 'onchapter':None,'chapters':6,'status':'Ongoing'}
#What would I Replace x with to allow a
#dictionary to connect to the values?
cur.execute('INSERT INTO Media VALUES (NULL, x)'), values)
cur.execute('SELECT * FROM Media')
meida = cur.fetchone()
print meida
If you're trying to use a dict to specify both the column names and the values, you can't do that, at least not directly.
That's really inherent in SQL. If you don't specify the list of column names, you have to specify them in CREATE TABLE order—which you can't do with a dict, because a dict has no order. If you really wanted to, of course, you could use a collections.OrderedDict, make sure it's in the right order, and then just pass values.values(). But at that point, why not just have a list (or tuple) in the first place? If you're absolutely sure you've got all the values, in the right order, and you want to refer to them by order rather than by name, what you have is a list, not a dict.
And there's no way to bind column names (or table names, etc.) in SQL, just values.
You can, of course, generate the SQL statement dynamically. For example:
columns = ', '.join(values.keys())
placeholders = ', '.join('?' * len(values))
sql = 'INSERT INTO Media ({}) VALUES ({})'.format(columns, placeholders)
values = [int(x) if isinstance(x, bool) else x for x in values.values()]
cur.execute(sql, values)
However, this is almost always a bad idea. This really isn't much better than generating and execing dynamic Python code. And you've just lost all of the benefits of using placeholders in the first place—primarily protection from SQL injection attacks, but also less important things like faster compilation, better caching, etc. within the DB engine.
It's probably better to step back and look at this problem from a higher level. For example, maybe you didn't really want a static list of properties, but rather a name-value MediaProperties table? Or, alternatively, maybe you want some kind of document-based storage (whether that's a high-powered nosql system, or just a bunch of JSON or YAML objects stored in a shelve)?
An alternative using named placeholders:
columns = ', '.join(my_dict.keys())
placeholders = ':'+', :'.join(my_dict.keys())
query = 'INSERT INTO my_table (%s) VALUES (%s)' % (columns, placeholders)
print query
cur.execute(query, my_dict)
con.commit()
There is a solution for using dictionaries. First, the SQL statement
INSERT INTO Media VALUES (NULL, 'x');
would not work, as it assumes you are referring to all columns, in the order they are defined in the CREATE TABLE statement, as abarnert stated. (See SQLite INSERT.)
Once you have fixed it by specifying the columns, you can use named placeholders to insert data. The advantage of this is that is safely escapes key-characters, so you do not have to worry. From the Python sqlite-documentation:
values = {
'title':'jack', 'type':None, 'genre':'Action',
'onchapter':None,'chapters':6,'status':'Ongoing'
}
cur.execute(
'INSERT INTO Media (id, title, type, onchapter, chapters, status)
VALUES (:id, :title, :type, :onchapter, :chapters, :status);',
values
)
You could use named parameters:
cur.execute('INSERT INTO Media VALUES (NULL, :title, :type, :genre, :onchapter, :chapters, :status)', values)
This still depends on the column order in the INSERT statement (those : are only used as keys in the values dict) but it at least gets away from having to order the values on the python side, plus you can have other things in values that are ignored here; if you're pulling what's in the dict apart to store it in multiple tables, that can be useful.
If you still want to avoid duplicating the names, you could extract them from an sqlite3.Row result object, or from cur.description, after doing a dummy query; it may be saner to keep them around in python form near wherever you do your CREATE TABLE.
Here's a more generic way with the benefit of escaping:
# One way. If keys can be corrupted don't use.
sql = 'INSERT INTO demo ({}) VALUES ({})'.format(
','.join(my_dict.keys()),
','.join(['?']*len(my_dict)))
# Another, better way. Hardcoded w/ your keys.
sql = 'INSERT INTO demo ({}) VALUES ({})'.format(
','.join(my_keys),
','.join(['?']*len(my_dict)))
cur.execute(sql, tuple(my_dict.values()))
key_lst = ('status', 'title', 'chapters', 'onchapter', 'genre', 'type')
cur.execute('INSERT INTO Media (status,title,chapters,onchapter,genre,type) VALUES ' +
'(?,?,?,?,?,?);)',tuple(values[k] for k in key_lst))
Do your escaping right.
You probably also need a commit call in there someplace.
Super late to this, but figured I would add my own answer. Not an expert, but something I found that works.
There are issues with preserving order when using a dictionary, which other users have stated, but you could do the following:
# We're going to use a list of dictionaries, since that's what I'm having to use in my problem
input_list = [{'a' : 1 , 'b' : 2 , 'c' : 3} , {'a' : 14 , 'b' : '' , 'c' : 43}]
for i in input_list:
# I recommend putting this inside a function, this way if this
# Evaluates to None at the end of the loop, you can exit without doing an insert
if i :
input_dict = i
else:
input_dict = None
continue
# I am noting here that in my case, I know all columns will exist.
# If you're not sure, you'll have to get all possible columns first.
keylist = list(input_dict.keys())
vallist = list(input_dict.values())
query = 'INSERT INTO example (' +','.join( ['[' + i + ']' for i in keylist]) + ') VALUES (' + ','.join(['?' for i in vallist]) + ')'
items_to_insert = list(tuple(x.get(i , '') for i in keylist) for x in input_list)
# Making sure to preserve insert order.
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.executemany(query , items_to_insert)
conn.commit()
dictionary = {'id':123, 'name': 'Abc', 'address':'xyz'}
query = "insert into table_name " + str(tuple(dictionary.keys())) + " values" + str(tuple(dictionary.values())) + ";"
cursor.execute(query)
query becomes
insert into table_name ('id', 'name', 'address') values(123, 'Abc', 'xyz');
I was having the similar problem so I created a string first and then passed that string to execute command. It does take longer time to execute but mapping was perfect for me. Just a work around:
create_string = "INSERT INTO datapath_rtg( Sr_no"
for key in record_tab:
create_string = create_string+ " ," + str(key)
create_string = create_string+ ") VALUES("+ str(Sr_no)
for key in record_tab:
create_string = create_string+ " ," + str(record_tab[key])
create_string = create_string + ")"
cursor.execute(create_string)
By doing above thing I ensured that if my dict (record_tab) doesn't contain a particular field then the script wont throw out error and proper mapping can be done which is why I used dictionary at the first place.
I was having a similar problem and ended up with something not entirely unlike the following (Note - this is the OP's code with bits changed so that it works in the way they requested)-
import sqlite3
db = sqlite3.connect('local.db')
cur = db.cursor()
cur.execute('DROP TABLE IF EXISTS Media')
cur.execute('''CREATE TABLE IF NOT EXISTS Media(
id INTEGER PRIMARY KEY, title TEXT,
type TEXT, genre TEXT,
onchapter INTEGER, chapters INTEGER,
status TEXT
)''')
values = {'title':'jack', 'type':None, 'genre':'Action', 'onchapter':None,'chapters':6,'status':'Ongoing'}
#What would I Replace x with to allow a
#dictionary to connect to the values?
#cur.execute('INSERT INTO Media VALUES (NULL, x)'), values)
# Added code.
cur.execute('SELECT * FROM Media')
colnames = cur.description
list = [row[0] for row in cur.description]
new_list = [values[i] for i in list if i in values.keys()]
sql = "INSERT INTO Media VALUES ( NULL, "
qmarks = ', '.join('?' * len(values))
sql += qmarks + ")"
cur.execute(sql, new_list)
#db.commit() #<-Might be important.
cur.execute('SELECT * FROM Media')
media = cur.fetchone()
print (media)

Python SQL Select with possible NULL values

I'm new to python and have hit a problem with an SQL query I'm trying to perform.
I am creating an SQL SELECT statement that is populated with values from an array as follows:
ret = conn.execute('SELECT * FROM TestTable WHERE a = ? b = ? c = ?', *values)
This works ok where I have real values in the values array. However in some cases an individual entry in values may be set to None. The query then fails because the "= NULL" test does not work since the test should be IS NULL.
Is there an easy way around this?
If you are using SQL Server then as long as you set ANSI_NULLS off for the session '= null' comparison will work.
SET ANSI_NULLS
Use : "Select * from testtable where (a = ? or a is null) and (b=? or b is null) "
This will select cases where a exactly matches the supplied value and will include the null values in the column - if that is what you want.
You can always use the ternary operator to switch '=' for 'IS':
("=","IS")[var is None]
Would return "IS" if var is None and "=" otherwise.
It's not very elegant to do this in one line though, but just for demonstrating:
query = "SELECT * FROM testTable WHERE a %s %s" % ( ("=","IS")[a is None], str(a) )
If you're feeling adventurous, you could also check out SQLAlchemy. It provides amongst a lot of other things an SQL construction toolkit that automatically converts comparisons to None into IS NULL operations.
First and foremost, I would strongly caution you against using SELECT * FROM tbl WHERE (col = :x OR col IS NULL) as this will most likely disqualify your query from indexing (use a query profiler). SET ANSI_NULLS is also one of those things that may not be supported in your particular database type.
A better solution (or at least a more universal solution) is, if your SQL dialect supports Coalesce, to write your query like this:
SELECT * FROM tbl WHERE col = COALESCE(:x, col)
Since col = col will always evaluate to true, passing in NULL for :x won't damage your query, and should allow for a more efficient query plan. This also has the advantage that it works from within a stored procedure, where you may not have the liberty of dynamically building a query string.
The following was tested on python sqlite3 by now, however it should work in other DB types since it quite general. The approach is the same to #MoshiBin answer with some additions:
Here is the form using cursor.execute() regular syntax, so the null variables is not supported while using this form:
import sqlite3
conn = sqlite3.connect('mydbfile.db')
c = conn.cursor()
c.execute('SELECT * FROM TestTable WHERE colname = ?', (a, ))
In order to support null variables you may replace the 4th line to:
request = 'SELECT * FROM TestTable WHERE colname %s' % ('= ' + str(a) if a else 'IS NULL')
c.execute(request)
Besides that if the variable is in text type, you also need to include a quotes:
request = "SELECT * FROM TestTable WHERE colname %s" % ("= '" + a + "'" if a else 'IS NULL')
Finaly if a variable can contain a single quotes itself, you also need to escape it by doubling:
request = "SELECT * FROM TestTable WHERE colname %s" % ("= '" + a.replace("'", "''") + "'" if a else 'IS NULL')
Edit:
Lately I have found two other approaches that also can be used in this case and uses regular cursor.execute() syntax, however I did't test this ones by now:
c.execute('SELECT * FROM TestTable WHERE colname = :1 OR (:1 IS NULL AND colname IS NULL)', {'1': a})
(Thx to #BillKarwin answer)
or, using the CASE expression:
c.execute('SELECT * FROM TestTable WHERE CASE :1 WHEN NOT NULL THEN colname = :1 ELSE colname IS NULL END', {'1': a})
It worked on my end.
with mysqlcon.cursor() as cursor:
sql = "SELECT * \
FROM `books` \
WHERE `date` is NULL"
cursor.execute(sql)
books = cursor.fetchall()
return books

Categories