MySQLdb不返回所有使用"on duplicate key update"转换的参数

Hua*_* Li 5 python mysql-python

使用python中的MySQLdb包,我想插入记录并检查一些唯一键.我使用的方法是executemany.参数是sql语句和元组.但是当我执行它时,它引发了一个错误,表示"并非所有参数都被转换".代码如下:

dData = [[u'Daniel', u'00-50-56-C0-00-12', u'Daniel']]
sql = "INSERT INTO app_network_white_black_list (biz_id, shop_id, type, mac_phone, remarks, create_time) " \
      "VALUES ({bsid}, {shop_id}, {type}, %s, %s, NOW()) " \
      "ON DUPLICATE KEY UPDATE type={type}, remarks=%s, create_time=NOW()".format(bsid=bsid, shop_id=shop_id, type=dType)
cur.executemany(sql, tuple(dData))
Run Code Online (Sandbox Code Playgroud)

有人说这是一个错误.但他们并没有给我一条跳过它的途径.如果这是一个错误,请提供方法.

Air*_*Air 27

出了什么问题

在检查下面评论中的链接并进行更多研究和测试后,我能够使用MySQLdb版本1.2.4b4和1.2.5重现错误.正如unubtu的回答中所解释,这与出现在其中的正则表达式的局限性有关cursors.py.每个版本中的确切正则表达式略有不同,可能是因为人们不断寻找它无法处理的案例并调整表达式,而不是完全寻找更好的方法.

正则表达式的作用是尝试匹配语句的VALUES ( ... )子句INSERT并标识它包含的元组表达式的开头和结尾.如果匹配成功,则executemany尝试将单行插入语句模板转换为多行插入语句,以使其运行得更快.即,而不是为要插入的每一行执行此操作:

INSERT INTO table
  (foo, bar, ...)
VALUES
  (%s, %s, ...);
Run Code Online (Sandbox Code Playgroud)

它尝试重写语句,以便它只需执行一次:

INSERT INTO table
  (foo, bar, ...)
VALUES
  (1, 2, ...),
  (3, 4, ...),
  (5, 6, ...),
  ...;
Run Code Online (Sandbox Code Playgroud)

您遇到的问题是executemany假设您之后立即只在元组中有参数占位符VALUES.如果您以后也有占位符,则需要:

INSERT INTO table
  (foo, bar, ...)
VALUES
  (%s, %s, ...)
ON DUPLICATE KEY UPDATE baz=%s;
Run Code Online (Sandbox Code Playgroud)

并试图像这样重写它:

INSERT INTO table
  (foo, bar, ...)
VALUES
  (1, 2, ...),
  (3, 4, ...),
  (5, 6, ...),
  ...
ON DUPLICATE KEY UPDATE baz=%s;
Run Code Online (Sandbox Code Playgroud)

这里的问题是MySQLdb试图在重写查询的同时进行字符串格式化.只VALUES ( ... )需要重写该子句,因此MySQLdb会尝试将所有参数放入匹配组中(%s, %s, ...),而不是意识到某些参数需要进入UPDATE子句.

如果您只发送VALUES子句的参数executemany,您将避免TypeError但遇到另一个问题.请注意,重写的INSERT ... ON DUPLICATE UPDATE查询在VALUES子句中有数字文字,但子句中仍有一个%s占位符UPDATE.当它到达MySQL服务器时,它会抛出语法错误.

当我第一次测试您的示例代码时,我使用的是MySQLdb 1.2.3c1,无法重现您的问题.有趣的是,包的特定版本避免这些问题的原因是正则表达式被破坏并且根本不符合语句.由于它不匹配,executemany不会尝试重写查询,而只是循环遍历您的参数execute重复调用.

怎么办呢

首先,不要回去安装1.2.3c1来完成这项工作.您希望尽可能使用更新的代码.

您可以移动到另一个包,正如unubtu在链接的问答中所暗示的那样,但这将涉及一些调整并可能更改其他代码.

我建议的是以更直接的方式重写查询并利用子句中的VALUES()函数UPDATE.此功能允许您指回到你的价值观已插入在没有重复键冲突的,按列名(例子是在MySQL的文档).

考虑到这一点,这是一种方法:

dData = [[u'Daniel', u'00-50-56-C0-00-12', u'Daniel']]  # exact input you gave

sql = """
INSERT INTO app_network_white_black_list
  (biz_id, shop_id, type, mac_phone, remarks, create_time)
VALUES
  (%s, %s, %s, %s, %s, NOW())
ON DUPLICATE KEY UPDATE
  type=VALUES(type), remarks=VALUES(remarks), create_time=VALUES(create_time);
"""  # keep parameters in one part of the statement

# generator expression takes care of the repeated values
cur.executemany(sql, ((bsid, shop_id, dType, mac, rem) for mac, rem in dData))
Run Code Online (Sandbox Code Playgroud)

这种方法应该有效,因为UPDATE子句中没有参数,这意味着MySQLdb将能够成功地将带有参数的单行插入模板转换为带有文字值的多行插入语句.

有些事情需要注意:

  • 你不必提供一个元组executemany; 任何迭代都没问题.
  • 多行字符串使Python代码中的可读SQL语句比隐式连接字符串更易读; 当您将语句与字符串分隔符分开时,很容易快速获取语句并将其复制到客户端应用程序中进行测试.
  • 如果您要参数化部分查询,为什么不参数化所有查询?即使只是用户输入的一部分,以相同的方式处理所有输入值也更具可读性和可维护性.
  • 也就是说,我没有参数化NOW().我在这里的首选方法是使用CURRENT_TIMESTAMP列默认值并DEFAULT在语句中使用.其他人可能更喜欢在应用程序中生成此值并将其作为参数提供.如果您不担心版本兼容性,它可能会很好.
  • 如果你不能避免在UPDATE子句中有参数占位符- 例如,因为UPDATE值不能在语句中硬编码或从VALUES元组派生- 你将不得不迭代execute而不是使用executemany.