MySQLdb no devuelve todos los argumentos convertidos con “en la actualización de clave duplicada”

Con el paquete MySQLdb en python, quiero insertar registros con la comprobación de algunas claves únicas. El método que utilicé es ejecutable. Los argumentos son oración sql y una tupla. Pero cuando lo ejecuté, apareció un error que decía “no todos los argumentos se convirtieron”. Los códigos son los siguientes:

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

Alguien dijo que esto es un error. Pero no me dieron un camino para saltar por encima. Por favor, proporcione un método si esto es un error.

Que esta mal

Después de revisar el enlace en su comentario a continuación y hacer más investigación y pruebas, pude reproducir el error con las versiones 1.2.4b4 y 1.2.5 de MySQLdb. Como se explica en la respuesta de Unubtu , esto tiene que ver con las limitaciones de una expresión regular que aparece en cursors.py . La expresión regular exacta es ligeramente diferente en cada versión, probablemente porque las personas siguen encontrando casos que no manejan y ajustan la expresión en lugar de buscar un enfoque mejor por completo.

Lo que hace la expresión regular es intentar hacer coincidir la cláusula VALUES ( ... ) de la INSERT e identificar el principio y el final de la expresión de tupla que contiene. Si la coincidencia se realiza correctamente, executemany intentan convertir la plantilla de instrucción de inserción de una sola fila en una instrucción de inserción de varias filas para que se ejecute más rápido. Es decir, en lugar de ejecutar esto para cada fila que desee insertar:

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

Intenta volver a escribir la statement para que solo tenga que ejecutarse una vez:

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

El problema con el que se está ejecutando es que la executemany asume que solo tiene marcadores de posición de parámetros en la tupla inmediatamente después de VALUES . Cuando también tienes marcadores de posición más adelante, toma esto:

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

Y trata de reescribirlo así:

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

El problema aquí es que MySQLdb está tratando de dar formato a la cadena al mismo tiempo que está reescribiendo la consulta. Solo se debe volver a escribir la cláusula VALUES ( ... ) , por lo que MySQLdb intenta colocar todos sus parámetros en el grupo correspondiente (%s, %s, ...) , sin darse cuenta de que algunos parámetros deben ingresar en la cláusula UPDATE en lugar.

Si solo envías parámetros para la cláusula VALUES a la executemany , executemany el TypeError pero te encontrarás con un problema diferente. Observe que la consulta reescrita INSERT ... ON DUPLICATE UPDATE tiene literales numéricos en la cláusula VALUES , pero todavía hay un marcador de posición %s en la cláusula UPDATE . Eso va a lanzar un error de syntax cuando llegue al servidor MySQL.

Cuando probé por primera vez su código de muestra, estaba usando MySQLdb 1.2.3c1 y no pude reproducir su problema. Curiosamente, la razón por la que una versión particular del paquete evita estos problemas es que la expresión regular está rota y no coincide con la afirmación. Como no coincide, executemany no intentan volver a escribir la consulta y, en su lugar, recorren los parámetros que execute repetidamente.

Qué hacer al respecto

En primer lugar, no vuelva atrás e instale 1.2.3c1 para que esto funcione. Desea utilizar código actualizado siempre que sea posible.

Podría moverse a otro paquete, como sugiere Unubtu en las preguntas y respuestas vinculadas, pero eso implicaría una cierta cantidad de ajuste y posiblemente cambios en otro código.

Lo que recomendaría en su lugar es reescribir su consulta de una manera que sea más sencilla y que aproveche la función VALUES() en su cláusula UPDATE . Esta función le permite hacer referencia a los valores que hubiera insertado en ausencia de una violación de clave duplicada, por nombre de columna (los ejemplos se encuentran en los documentos de MySQL ).

Con eso en mente, aquí hay una manera de hacerlo:

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

Este enfoque debería funcionar porque no hay parámetros en la cláusula UPDATE , lo que significa que MySQLdb podrá convertir con éxito la plantilla de inserción de una sola línea con parámetros en una statement de inserción de varias líneas con valores literales.

Algunas cosas a tener en cuenta:

  • Usted no tiene que suministrar una tupla a la executemany ; cualquier iterable esta bien
  • Las cadenas multilínea crean sentencias de SQL mucho más legibles en su código de Python que las cadenas concatenadas implícitamente; cuando separa la statement de los delimitadores de cadena, es fácil tomar la statement rápidamente y copiarla en una aplicación cliente para su prueba.
  • Si va a parametrizar parte de su consulta, ¿por qué no parametrizar toda su consulta? Incluso si solo una parte es la entrada del usuario, es más fácil de leer y mantener para manejar todos los valores de entrada de la misma manera.
  • Dicho esto, no parametricé NOW() . Mi enfoque preferido aquí sería utilizar CURRENT_TIMESTAMP como valor predeterminado de la columna y aprovechar el valor DEFAULT en la statement. Otros pueden preferir generar este valor en la aplicación y suministrarlo como un parámetro. Si no estás preocupado por la compatibilidad de versiones, probablemente esté bien como está.
  • Si no puede evitar tener marcadores de posición de parámetros en la cláusula UPDATE , por ejemplo, debido a que los valores de UPDATE no se pueden codificar en la statement o derivar de la tupla VALUES , tendrá que iterar sobre la execute lugar de utilizando executemany .

Tiene tres elementos en dData pero solo dos %s posición para que entren.