使用MySQLdb执行"SELECT ... WHERE ... IN ..."

unu*_*tbu 52 python mysql

我在Python中执行某些SQL时遇到问题,尽管类似的SQL在mysql命令行中工作正常.

该表如下所示:

mysql> SELECT * FROM foo;
+-------+-----+
| fooid | bar |
+-------+-----+
|     1 | A   | 
|     2 | B   | 
|     3 | C   | 
|     4 | D   | 
+-------+-----+
4 rows in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

我可以从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)
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试在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)
# ()
Run Code Online (Sandbox Code Playgroud)

所以,问题是:应该如何Python代码进行修改,以选择那些fooidS其中bar('A','C')

顺便说一句,我注意到,如果我切换的角色barfooid,我可以得到代码选择那些barS其中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',))
Run Code Online (Sandbox Code Playgroud)

而且为了绝对清楚,这就是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
Run Code Online (Sandbox Code Playgroud)

编辑:当我启用常规查询日志时,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
Run Code Online (Sandbox Code Playgroud)

事实上,看起来有太多的报价被放置在A和周围C.

感谢@ Amber的评论,我更清楚地知道出了什么问题.MySQLdb将参数化参数转换['A','C']("'A'","'C'").

有没有办法使用INSQL语法进行参数化查询?或者必须手动构造SQL字符串?

Joã*_*lva 67

不幸的是,你需要手动构造查询参数,因为据我所知,目前还没有内置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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

  • 这将容易sql注入.我们能做的更安全吗? (17认同)
  • @Sohaib如何容易注射?用于查询的数据(args)与sql文本分开传递.字符串操作只会添加更多占位符'%s',而不会增加易受攻击的实际数据. (8认同)
  • `in_p =','.join(['%s']*len(args))`,而我们在... (4认同)
  • `in_p =','.join(itertools.repeat('%s',len(args)))` (3认同)
  • 具有SQL Inyection漏洞,在SQL安全中不能转义参数。mysql.exec使用查询字符串和参数,对参数进行了转义,但是通过手动联接却无法转义。示例:在参数中添加双引号,反斜杠,换行符,通配符,退格键等,字符集问题,例如,在utf8中定义连接,但查询在日本文本编码中转义,等等。https://es.slideshare .net / openpbs / sql-injection-defense-in-python。有多少人违反了他们的发展而实施了该解决方案? (2认同)
  • 任何因为 SQL 注入漏洞而投反对票的人——你都错了。即使在之前的编辑和最初的答案中,该答案也不包含此漏洞。请仔细阅读答案。查询的参数已正确传递给“execute”,所有格式都需要正确计数“IN”子句中初始“%s”出现的次数。 (2认同)

小智 43

这是一个类似的解决方案,我认为在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))
Run Code Online (Sandbox Code Playgroud)

这样你就可以避免引用自己,并避免各种sql注入.

请注意,data(list_of_ids)直接作为参数(不在查询文本中)直接转到mysql的驱动程序,因此没有注入.你可以在字符串中留下你想要的任何字符,不需要删除或引用字符.

  • 同意 - 这是一个更好的解决方案.上面打开了SQL注入攻击,例如,如果用户输入`); 从用户中选择用户名,密码,credit_card;`他们的代码可以针对数据库运行. (3认同)
  • 我喜欢这个,我会尽可能地说接受的答案被认为是有害的! (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))
Run Code Online (Sandbox Code Playgroud)

也就是说,将所有参数连接在一个线性数组中,然后将其作为元组传递给execute方法.


小智 5

这对我有用:

myTuple= tuple(myList)
sql="select fooid from foo where bar in "+str(myTuple)
cursor.execute(sql)
Run Code Online (Sandbox Code Playgroud)

  • 永远不要直接在SQL查询中传递参数!这导致SQL注入漏洞,这是一个主要的安全问题.应始终在参数元组中传递参数,以便SQL绑定库正确引用它们.不同的引擎具有不同的引用规则,引用参数的唯一安全方法是让较低级别的库执行此操作. (2认同)