使用MySQLdb执行“SELECT … WHERE … IN …”
我在执行Python中的一些SQL时遇到问题,尽pipe类似的SQL可以在mysql
命令行下正常工作。
表格看起来像这样:
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代码来select那些在哪里('A','C')
fooid
?
顺便说一句,我注意到,如果我切换bar
和fooid
的angular色,我可以得到代码来成功地selectfooid
所在的那些bar
(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',))
而要清楚的是,这是如何创buildfoo
表:
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语法进行参数化查询? 或者必须手动构buildSQLstring?
不幸的是,你需要手动构造查询参数,因为据我所知,没有内置的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)
下面是一个类似的解决scheme ,我认为在SQL中build立%sstring列表的效率更高:
直接使用
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的驱动程序,作为参数(不在查询文本中),所以没有注入。 你可以留下string中的任何字符,不需要删除或引用字符。
如果您在查询中有其他参数,则不在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方法。
这对我有用:
myTuple= tuple(myList) sql="select fooid from foo where bar in "+str(myTuple) cursor.execute(sql)
也许我们可以创build一个function去做João提出的build议? 就像是:
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)
一直在尝试João的解决scheme的每一个变种,以获得一个IN列表查询与Tornado的mysql包装,并仍然得到可信的“TypeError:没有足够的格式string参数”错误。 原来在列表中添加“*”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)
为了改进João和satru的代码,我build议创build一个游标混搭,可以使用一个接受嵌套迭代的execute来构build一个游标,并正确处理它们。 一个更好的名字是好的,但是…对于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',))
为什么不在这种情况下呢?
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')
参数应该是元组。
例如:
args =('A','B')
args =('A',)#在单个的情况下