【问题标题】:Executing "SELECT ... WHERE ... IN ..." using MySQLdb使用 MySQLdb 执行“SELECT ... WHERE ... IN ...”
【发布时间】:2011-06-02 06:08:34
【问题描述】:

我在 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)

我可以从 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代码以选择bar('A','C')中的那些fooids?

顺便说一句,我注意到如果我切换barfooid 的角色,我可以得到代码来选择fooid(1,3) 中的那些bars。我不明白为什么一个这样的查询(下)有效,而另一个(上)没有。

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

确实,AC 周围似乎有太多引号。

感谢@Amber 的评论,我更了解出了什么问题。 MySQLdb 将参数化参数['A','C'] 转换为("'A'","'C'")

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

【问题讨论】:

  • 您能否查看 MySQL 服务器的日志并找出实际运行的查询?它似乎与如何解释字符串/varchars 有关(因为 python int -> mysql int 工作正常)。

标签: python mysql


【解决方案1】:

不幸的是,您需要手动构造查询参数,因为据我所知,没有内置的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)

【讨论】:

  • in_p = ', '.join(itertools.repeat('%s', len(args)))
  • 这样容易出现sql注入。难道我们不能做一些更安全的事情吗?
  • @Sohaib 这怎么会容易被注入?用于查询的数据 (args) 与 sql 文本分开传递。字符串操作只会添加更多占位符“%s”,而不是容易受到攻击的实际数据。
  • in_p = ', '.join(['%s']*len(args)),而我们在这……
  • 有SQL Inyection 漏洞,参数在sql secuence 中没有转义。 mysql.exec 使用查询字符串和参数,参数被转义,但手动连接它不会被转义。示例:在参数中添加双引号、反斜杠、换行符、通配符、退格符等,字符集问题,例如,在 utf8 中定义连接,但查询在日本文本编码等中被转义。es.slideshare.net/openpbs/sql-injection-defense-in-python。有多少人违反他们的发展实施了这个解决方案?
【解决方案2】:

在那种情况下为什么不只是这个?

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')

【讨论】:

  • 我想我已经回答了我自己的问题:如果您单独传递参数,它们将被 Mysqldb 库正确转义。
  • 永远不要在 SQL 查询中直接传递参数!这会导致 SQL 注入漏洞,这是一个主要的安全问题。参数应始终在参数元组中传递,以便 SQL 绑定库正确引用它们。不同的引擎有不同的引用规则,唯一安全的引用参数的方法就是让低级库来做。
【解决方案3】:

如果您在查询中有其他参数,除了 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))

也就是说,将所有参数加入一个线性数组中,然后将其作为元组传递给执行方法。

【讨论】:

    【解决方案4】:

    也许我们可以创建一个函数来做 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】:

      这是一个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 的驱动程序,因此没有注入。您可以在字符串中保留任何您想要的字符,无需删除或引用字符。

      【讨论】:

      • 我喜欢这个,我什至会说接受的答案被认为是有害的!
      • 同意 - 这是一个更好的解决方案。上面的内容让您面临 SQL 注入攻击,例如,如果用户键入 ); select username, password, credit_card from user;,他们的代码可能会针对数据库运行。
      【解决方案6】:

      一直在尝试 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)
      

      【讨论】:

        【解决方案7】:

        对 João 和 satru 的代码进行改进,我建议创建一个游标混合,可用于构建具有执行的游标,该执行接受嵌套迭代并正确处理它们。不过,一个更好的名字会更好……对于 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',))
        

        【讨论】:

          【解决方案8】:

          这对我有用:

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

          【讨论】:

          • 永远不要在 SQL 查询中直接传递参数!这会导致 SQL 注入漏洞,这是一个主要的安全问题。参数应始终在参数元组中传递,以便 SQL 绑定库正确引用它们。不同的引擎有不同的引用规则,唯一安全的引用参数的方法就是让低级库来做。
          【解决方案9】:

          args 应该是元组。

          例如:

          args = ('A','B')
          
          args = ('A',) # in case of single
          

          【讨论】:

            【解决方案10】:

            很简单:

            只需使用下面的格式###

            rules_id = ["9","10"]
            
            sql2 = "SELECT * FROM attendance_rules_staff WHERE id in"+str(tuple(rules_id))
            

            注意str(tuple(rules_id))

            【讨论】:

            • 一个优雅的解决方案,只要rules_id 从不来自外部来源。如果您系统之外的任何人都可以设置rules_id 的值,那么您的数据库将容易受到 SQL 注入的攻击。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-21
            • 1970-01-01
            • 2018-08-14
            • 2011-11-24
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多