【问题标题】:Mysql named placeholders in python used in the IN clause在IN子句中使用的python中的Mysql命名占位符
【发布时间】:2013-07-23 11:44:53
【问题描述】:

在破解 MySQL 绑定的 python 代码时,我更喜欢使用命名占位符,但似乎我无法使用 IN 子句来恰到好处。一个例子:

con = MySQLdb.connect(db='test', user='test')
cur = con.cursor()

三个过于简单的例子:

# (#1) works fine, but I want placeholders.
cur.execute( """ update test
                 set    i = 999
                 where  SNO in (1, 2) """)

# (#2) works fine too, but still not enough placeholders.
cur.execute( """ update test
                 set    i = %(i)s
                 where  SNO in (1, 2) """, {'i' : 999})

# (#3) works, but did not pass the beauty check...
cur.execute( """ update test
                 set    i = %(i)s
                 where  SNO in ( %(a)s, %(b)s ) """, {'i' : 99,
                                                      'a' : 1,
                                                      'b' : 2})

这是我真正想要的,但它失败了:操作数应该包含 1 列

# (#4) This one fails with: _mysql_exceptions.OperationalError: (1241, 'Operand should contain 1 column(s)')
cur.execute( """ update test
                 set    i = %(i)s
                 where  SNO in ( %(foo)s ) """, {'i'   : 999,
                                                 'foo' : [1, 2]})

显然我需要更多的魔法。将问题转移到应用程序中很容易,在 python 中实现一个循环,但我宁愿避免这样做。

是的,性能也很重要。

【问题讨论】:

  • ",".join(['1', '2']) 而不是 [1,2] 怎么样? (适用于普通的 python 占位符)
  • 出于安全原因,我认为最好使用光标占位符
  • 是的,但是,作为一种解决方法,您可以在查询参数上调用 mysqldb.escapestring

标签: python mysql python-2.7


【解决方案1】:

MySQLdb 已经为您处理了序列的转义:

>>> con = MySQLdb.connect(db='test')
>>> con.literal([1,2,3])
('1', '2', '3')
>>> cur = con.cursor()
>>> cur.execute("select * from test where id in %(foo)s", {'foo': [1,2,3]})
3L
>>> cur._executed
"select * from test where id in ('1', '2', '3')"

因此,通过删除占位符周围的括号,它应该可以工作 - 但仅适用于具有多个元素的序列,因为单个元素的格式如下:

>>> con.literal([1])
('1',)

插入到 SQL 查询中,结尾的逗号使其成为非法 SQL。

要解决这个问题,您还可以定义自己的转换器来将自定义类型转换为您喜欢的表示:

import MySQLdb.converters
conv = MySQLdb.converters.conversions.copy()

class CustomList(list):
    def __init__(self, *items):
        super(CustomList, self).__init__(items)

conv[CustomList] = lambda lst, conv: "(%s)" % ', '.join(str(item) for item in lst)

con = MySQLdb.connect(db='test', conv=conv)
cur = con.cursor()
cur.execute('select * from test where id in %(foo)s', {'foo': CustomList(0, 1, 2)})
print cur._executed
select * from test where id in (0, 1, 2)

这样,列表项周围的引号就消失了。

也可以只替换 list 的转换器,但这会改变所有列表的行为并可能引入漏洞。上述格式化列表的方式对于包含字符串的列表是不安全的,因为它不会转义特殊字符。

为此,您必须递归地转义列表中的所有项目:

>>> ...
>>> conv[list] = lambda lst, cv: "(%s)" % ', '.join(cv[type(item)](item, cv) for item in lst)
>>> con = MySQLdb.connect(..., conv=conv)
>>> con.literal([1, "it's working...", 2])
"(1, 'it\\'s working...', 2)"

【讨论】:

  • 看准了!删除括号正是我所缺少的magic。关于自定义转换器,它们对我来说是新的,我在哪里需要/使用它们?我的代码似乎已经完美运行了......
  • converters 可用于支持自定义类型的转换。在这种情况下,它并不是真正需要的,它只是展示如何控制自定义类型的转换。不同之处在于,在第一种情况下,每个 int 都被引用(字符串文字),而在第二种情况下则不是(整数文字)。
  • 虽然当我向IN 子句提供多个项目时它工作得很好,但如果列表(或元组)只包含一个元素,它就会失败。
  • 啊,那是因为它被格式化为元组,所以单个元素被格式化为例如作为('1',) - 尾随逗号使其非法sql ...因此,我的第二个解决方案会更好。我会将此信息添加到答案中。
  • +1 很难找到与 MySQLdb / mysqlclient 进行参数绑定的惯用示例。这是我在PEP249 上能找到的所有信息
猜你喜欢
  • 2011-11-17
  • 2017-05-09
  • 2019-06-04
  • 2014-11-29
  • 2015-07-10
  • 2014-10-07
  • 1970-01-01
  • 2012-05-23
相关资源
最近更新 更多