我在Python中执行某些SQL时遇到问题,尽管mysql
命令行中的SQL工作正常。
表格如下:
mysql> SELECT * FROM foo;
+-------+-----+
| fooid | bar |
+-------+-----+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
+-------+-----+
4 rows in set (0.00 sec)
我可以从mysql命令行执行以下SQL查询,没有问题:
mysql> SELECT fooid FROM foo WHERE bar IN ('A','C');
SELECT fooid FROM foo WHERE bar IN ('A','C');
+-------+
| fooid |
+-------+
| 1 |
| 3 |
+-------+
2 rows in set (0.00 sec)
然而,当我尝试在Python中做同样的事情时,我没有得到任何行,而我期望有2行:
import MySQLdb
import config
connection=MySQLdb.connect(
host=config.HOST,user=config.USER,passwd=config.PASS,db='test')
cursor=connection.cursor()
sql='SELECT fooid FROM foo WHERE bar IN %s'
args=[['A','C']]
cursor.execute(sql,args)
data=cursor.fetchall()
print(data)
# ()
所以问题是:如何修改python代码以选择fooid
bar
所在的('A','C')
?
顺便说一下,我注意到如果我切换bar
和fooid
的角色,我可以获取代码来选择bar
所在的fooid
(1,3)
成功了。我不明白为什么一个这样的查询(下面)有效,而另一个(上面)没有。
sql='SELECT bar FROM foo WHERE fooid IN %s'
args=[[1,3]]
cursor.execute(sql,args)
data=cursor.fetchall()
print(data)
# (('A',), ('C',))
为了绝对清楚,这就是foo
表的创建方式:
mysql> DROP TABLE IF EXISTS foo;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `foo` (
`fooid` int(11) NOT NULL AUTO_INCREMENT,
`bar` varchar(10) NOT NULL,
PRIMARY KEY (`fooid`));
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT into foo (bar) values ('A'),('B'),('C'),('D');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
修改:当我使用mysqld -l /tmp/myquery.log
启用常规查询日志时
我明白了
mysqld, Version: 5.1.37-1ubuntu5.5-log ((Ubuntu)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
110101 11:45:41 1 Connect unutbu@localhost on test
1 Query set autocommit=0
1 Query SELECT fooid FROM foo WHERE bar IN ("'A'", "'C'")
1 Query SELECT bar FROM foo WHERE fooid IN ('1', '3')
1 Quit
事实上,A
和C
周围的引号看起来太多了。
感谢@ Amber的评论,我更清楚地知道出了什么问题。 MySQLdb将参数化参数['A','C']
转换为("'A'","'C'")
。
有没有办法使用IN
SQL语法进行参数化查询?或者必须手动构造SQL字符串吗?
答案 0 :(得分:66)
不幸的是,您需要手动构造查询参数,因为据我所知,没有内置bind
方法将list
绑定到IN
子句,类似于Hibernate的setParameterList()
。但是,您可以通过以下方式完成相同的操作:
Python 3:
args=['A', 'C']
sql='SELECT fooid FROM foo WHERE bar IN (%s)'
in_p=', '.join(list(map(lambda x: '%s', args)))
sql = sql % in_p
cursor.execute(sql, args)
Python 2:
args=['A', 'C']
sql='SELECT fooid FROM foo WHERE bar IN (%s)'
in_p=', '.join(map(lambda x: '%s', args))
sql = sql % in_p
cursor.execute(sql, args)
答案 1 :(得分:42)
以下是similar solution,我认为在SQL中构建%s字符串列表更有效:
直接使用
list_of_ids
:format_strings = ','.join(['%s'] * len(list_of_ids)) cursor.execute("DELETE FROM foo.bar WHERE baz IN (%s)" % format_strings, tuple(list_of_ids))
这样你就可以避免引用自己,并避免使用各种sql注入。
请注意,数据(
list_of_ids
)将作为参数(不在查询文本中)直接转到mysql的驱动程序,因此没有注入。你可以在字符串中留下你想要的任何字符,不需要删除或引用字符。
答案 2 :(得分:10)
如果查询中有其他参数,超出IN列表,那么JG答案的以下扩展可能会有用。
ids = [1, 5, 7, 213]
sql = "select * from person where type=%s and id in (%s)"
in_ids = ', '.join(map(lambda x: '%s', ids))
sql = sql % ('%s', in_ids)
params = []
params.append(type)
params.extend(ids)
cursor.execute(sql, tuple(params))
即,将所有参数连接到线性数组中,然后将其作为元组传递给execute方法。
答案 3 :(得分:5)
这对我有用:
myTuple= tuple(myList)
sql="select fooid from foo where bar in "+str(myTuple)
cursor.execute(sql)
答案 4 :(得分:2)
也许我们可以创建一个函数来做João提出的建议?类似的东西:
def cursor_exec(cursor, query, params):
expansion_params= []
real_params = []
for p in params:
if isinstance(p, (tuple, list)):
real_params.extend(p)
expansion_params.append( ("%s,"*len(p))[:-1] )
else:
real_params.append(p)
expansion_params.append("%s")
real_query = query % expansion_params
cursor.execute(real_query, real_params)
答案 5 :(得分:2)
一直在尝试使用João解决方案的每个变体来获取IN List查询以使用Tornado的mysql包装器,并且仍然得到了诅咒“TypeError:没有足够的格式字符串参数”错误。结果是将“*”添加到列表var“* args”就可以了。
args=['A', 'C']
sql='SELECT fooid FROM foo WHERE bar IN (%s)'
in_p=', '.join(list(map(lambda x: '%s', args)))
sql = sql % in_p
db.query(sql, *args)
答案 6 :(得分:2)
改进João和satru的代码,我建议创建一个游标mixin,可用于构建一个带有execute的游标,该execute接受嵌套的iterables并正确处理它们。一个更好的名字会很好,但是......对于Python3,使用str
而不是basestring
。
from MySQLdb.cursors import Cursor
class BetterExecuteMixin(object):
"""
This mixin class provides an implementation of the execute method
that properly handles sequence arguments for use with IN tests.
Examples:
execute('SELECT * FROM foo WHERE id IN (%s) AND type=%s', ([1,2,3], 'bar'))
# Notice that when the sequence is the only argument, you still need
# a surrounding tuple:
execute('SELECT * FROM foo WHERE id IN (%s)', ([1,2,3],))
"""
def execute(self, query, args=None):
if args is not None:
try:
iter(args)
except TypeError:
args = (args,)
else:
if isinstance(args, basestring):
args = (args,)
real_params = []
placeholders = []
for arg in args:
# sequences that we treat as a single argument
if isinstance(arg, basestring):
real_params.append(arg)
placeholders.append('%s')
continue
try:
real_params.extend(arg)
placeholders.append(','.join(['%s']*len(arg)))
except TypeError:
real_params.append(arg)
placeholders.append('%s')
args = real_params
query = query % tuple(placeholders)
return super(BetterExecuteMixin, self).execute(query, args)
class BetterCursor(BetterExecuteMixin, Cursor):
pass
然后可以按如下方式使用它(它仍然向后兼容!):
import MySQLdb
conn = MySQLdb.connect(user='user', passwd='pass', db='dbname', host='host',
cursorclass=BetterCursor)
cursor = conn.cursor()
cursor.execute('SELECT * FROM foo WHERE id IN (%s) AND type=%s', ([1,2,3], 'bar'))
cursor.execute('SELECT * FROM foo WHERE id IN (%s)', ([1,2,3],))
cursor.execute('SELECT * FROM foo WHERE type IN (%s)', (['bar', 'moo'],))
cursor.execute('SELECT * FROM foo WHERE type=%s', 'bar')
cursor.execute('SELECT * FROM foo WHERE type=%s', ('bar',))
答案 7 :(得分:1)
只需使用以下格式###
rules_id = ["9","10"]
sql2 = "SELECT * FROM attendance_rules_staff WHERE id in"+str(tuple(rules_id))
请注意str(tuple(rules_id))
。
答案 8 :(得分:-1)
为什么不在这种情况下呢?
args = ['A', 'C']
sql = 'SELECT fooid FROM foo WHERE bar IN (%s)'
in_p =', '.join(list(map(lambda arg: "'%s'" % arg, args)))
sql = sql % in_p
cursor.execute(sql)
结果:
SELECT fooid FROM foo WHERE bar IN ('A', 'C')
答案 9 :(得分:-1)
args应该是元组。
例如:
sortMemberPath