Python MySQLdb并不是返回所有使用“转换”的参数;“关于重复密钥更新”;

Python MySQLdb并不是返回所有使用“转换”的参数;“关于重复密钥更新”;,python,mysql-python,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, rem

使用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))

有人说这是一只虫子。但是他们没有给我一条路可以跳过它。如果这是一个bug,请提供一个方法。

您在
dData
中有三个元素,但只有两个
%s
占位符可供它们进入。

发生了什么问题 在检查了链接并进行了更多的研究和测试之后,我能够用MySQLdb版本1.2.4b4和1.2.5重现错误。如中所述,这与出现在
cursors.py
中的正则表达式的限制有关。确切的正则表达式在每个版本中都略有不同,这可能是因为人们一直在寻找它不能处理的情况,并调整表达式,而不是完全寻找更好的方法

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

INSERT INTO table
  (foo, bar, ...)
VALUES
  (%s, %s, ...);
它尝试重写语句,以便只执行一次:

INSERT INTO table
  (foo, bar, ...)
VALUES
  (1, 2, ...),
  (3, 4, ...),
  (5, 6, ...),
  ...;
您遇到的问题是,
executemany
假定元组中只有紧跟在
值之后的参数占位符。当您以后也有占位符时,将采用以下方式:

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…)
,而没有意识到某些参数需要进入
UPDATE
子句

如果只将
VALUES
子句的参数发送到
executemany
,则可以避免
类型错误
,但会遇到另一个问题。请注意,重写的
INSERT。。。重复更新时
查询在
子句中有数字文本,但在
更新
子句中仍有一个
%s
占位符。当它到达MySQL服务器时,将抛出一个语法错误

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

怎么办 首先,不要回去安装1.2.3c1来实现这一点。您希望尽可能使用更新的代码

正如unubtu在链接的Q&A中所建议的那样,您可以转移到另一个包,但这将涉及一些调整,并可能对其他代码进行更改

相反,我建议您以更直接的方式重写查询,并利用
UPDATE
子句中的
VALUES()
函数。此函数允许您按列名(示例在中)返回在没有重复键冲突的情况下插入的值

考虑到这一点,这里有一种方法:

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))
这种方法应该可以工作,因为
UPDATE
子句中没有参数,这意味着MySQLdb将能够成功地将带有参数的单行insert模板转换为带有文本值的多行insert语句

需要注意的一些事项:

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

sql中的第三行还有%s。您的回答非常有用。我会检查我的源代码是否有bug。但在另一个问题中,他们发现这是由MySQLdb中的常规express错误引起的。下面的链接可能更清楚:@HualiangLi谢谢你的链接,这很有帮助。事实上,你是对的,它与正则表达式有关;有关更多详细信息,请参阅我的更新答案。(答案基本上是一样的。)回答得真好!谢谢你的解决方案!很好的解释,