MySQLdb不会返回所有使用"转换的重复键更新"

时间:2014-10-13 09:48:23

标签: 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))

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

2 个答案:

答案 0 :(得分:24)

出了什么问题

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

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

INSERT INTO table
  (foo, bar, ...)
VALUES
  (%s, %s, ...);

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

INSERT INTO table
  (foo, bar, ...)
VALUES
  (1, 2, ...),
  (3, 4, ...),
  (5, 6, ...),
  ...;

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

INSERT INTO table
  (foo, bar, ...)
VALUES
  (%s, %s, ...)
ON DUPLICATE KEY UPDATE baz=%s;

并试图像这样重写它:

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

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

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

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

该怎么办

首先,不要返回并安装1.2.3c1以使其工作。您希望尽可能使用更新的代码。

你可以移动到另一个软件包,正如unubtu在链接的Q& A中所暗示的那样,但这将涉及一些调整并可能更改其他代码。

我建议的是以更直接的方式重写查询,并利用execute子句中的VALUES()函数。此功能允许您通过列名称返回缺少重复键违规时 已插入的值(示例位于the MySQL docs)。

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

UPDATE

这种方法应该有效,因为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)) 子句中没有参数,这意味着MySQLdb将能够成功地将带参数的单行插入模板转换为带有文字值的多行插入语句。

有些注意事项:

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

答案 1 :(得分:-1)

dData中有三个元素,但只有两个%s个占位符供他们使用。