Consultas parametrizadas de MySQL

80

Estoy teniendo dificultades para usar el módulo MySQLdb para insertar información en mi base de datos. Necesito insertar 6 variables en la tabla.

cursor.execute ("""
    INSERT INTO Songs (SongName, SongArtist, SongAlbum, SongGenre, SongLength, SongLocation)
    VALUES
        (var1, var2, var3, var4, var5, var6)

""")

¿Alguien puede ayudarme con la sintaxis aquí?

Specto
fuente

Respuestas:

262

Tenga cuidado con el uso de interpolación de cadenas para consultas SQL, ya que no escapará de los parámetros de entrada correctamente y dejará su aplicación abierta a vulnerabilidades de inyección SQL. La diferencia puede parecer trivial, pero en realidad es enorme .

Incorrecto (con problemas de seguridad)

c.execute("SELECT * FROM foo WHERE bar = %s AND baz = %s" % (param1, param2))

Correcto (con escape)

c.execute("SELECT * FROM foo WHERE bar = %s AND baz = %s", (param1, param2))

Se suma a la confusión de que los modificadores utilizados para vincular parámetros en una declaración SQL varían entre las diferentes implementaciones de la API de base de datos y que la biblioteca cliente mysql utiliza una printfsintaxis de estilo en lugar del más comúnmente aceptado '?' marcador (usado por ej. python-sqlite).

Emil H
fuente
3
@Specto IMO tiene sentido permanecer siempre con las formas de implementación correctas y seguras. Crea hábitos adecuados y una buena cultura de programación. Además, nadie sabe cómo se utilizará su código en el futuro; alguien puede usarlo más tarde para otro sistema o sitio web.
Roman Podlinov
@BryanHunt ¿Puede activar el uso de? con un argumento en alguna parte, pero se desalienta porque no le dice mucho sobre qué argumento va a dónde. (Lo mismo podría decirse de% s, por supuesto, que se desaconseja por la misma razón). Más información aquí: python.org/dev/peps/pep-0249/#paramstyle
kqr
1
Viniendo de php / pdo, estaba mega confundido con el marcador de printfestilo %s, y estaba algo aterrorizado de estar escribiendo consultas vulnerables. ¡Gracias por aclarar esa preocupación! :)
Darragh Enright
18
Cuando es un solo parámetro, recuerde mantener la coma: c.execute("SELECT * FROM foo WHERE bar = %s", (param1,))
Marius Lian
1
Para obtener contexto sobre los parámetros en la ejecución del cursor (y por qué% s todavía es necesario), la referencia de la API para el cursor MySQLdb está en mysql-python.sourceforge.net/MySQLdb-1.2.2/public/…
RedBarron
50

Tienes algunas opciones disponibles. Querrá sentirse cómodo con la iterpolación de cadenas de Python. ¿Cuál es un término que podría tener más éxito buscando en el futuro cuando quiera saber cosas como esta?

Mejor para consultas:

some_dictionary_with_the_data = {
    'name': 'awesome song',
    'artist': 'some band',
    etc...
}
cursor.execute ("""
            INSERT INTO Songs (SongName, SongArtist, SongAlbum, SongGenre, SongLength, SongLocation)
            VALUES
                (%(name)s, %(artist)s, %(album)s, %(genre)s, %(length)s, %(location)s)

        """, some_dictionary_with_the_data)

Teniendo en cuenta que probablemente ya tenga todos sus datos en un objeto o diccionario, el segundo formato le conviene más. También apesta tener que contar "% s" apariciones en una cadena cuando tienes que volver y actualizar este método en un año :)

Trey Stout
fuente
2
Parece que el enfoque del diccionario funciona mejor en caso de que una determinada variable de enlace deba utilizarse en más de un lugar de la declaración SQL. Con el enfoque posicional, necesitamos pasar la variable tantas veces como se haga referencia, lo cual no es muy deseable.
codeforester
15

Los documentos vinculados dan el siguiente ejemplo:

   cursor.execute ("""
         UPDATE animal SET name = %s
         WHERE name = %s
       """, ("snake", "turtle"))
   print "Number of rows updated: %d" % cursor.rowcount

Entonces solo necesita adaptar esto a su propio código, ejemplo:

cursor.execute ("""
            INSERT INTO Songs (SongName, SongArtist, SongAlbum, SongGenre, SongLength, SongLocation)
            VALUES
                (%s, %s, %s, %s, %s, %s)

        """, (var1, var2, var3, var4, var5, var6))

(Si SongLength es numérico, es posible que deba usar% d en lugar de% s).

Marcel Guzman
fuente
1
es que esto funciona cuando el var1 y var2 tienen charecters como "o".
sheki
3
AFAIK, tienes que usar %sallí sin importar qué tipo. @sheki: Sí
ThiefMaster
7

En realidad, incluso si su variable (SongLength) es numérica, aún tendrá que formatearla con% s para vincular el parámetro correctamente. Si intenta utilizar% d, obtendrá un error. Aquí hay un pequeño extracto de este enlace http://mysql-python.sourceforge.net/MySQLdb.html :

Para realizar una consulta, primero necesita un cursor y luego puede ejecutar consultas en él:

c=db.cursor()
max_price=5
c.execute("""SELECT spam, eggs, sausage FROM breakfast
          WHERE price < %s""", (max_price,))

En este ejemplo, max_price = 5 ¿Por qué, entonces, usar% s en la cadena? Porque MySQLdb lo convertirá en un valor literal de SQL, que es la cadena '5'. Cuando finalice, la consulta dirá "... DONDE precio <5".

daSong
fuente
Sí, esto es extraño, de acuerdo ... crees que el formato "printf" significaría ... en realidad el formato printf y no solo usar% s en todas partes.
fthinker
6

Como alternativa a la respuesta elegida, y con la misma semántica segura de Marcel, aquí hay una forma compacta de usar un diccionario Python para especificar los valores. Tiene la ventaja de ser fácil de modificar a medida que agrega o elimina columnas para insertar:

  meta_cols=('SongName','SongArtist','SongAlbum','SongGenre')
  insert='insert into Songs ({0}) values ({1})'.
        .format(','.join(meta_cols), ','.join( ['%s']*len(meta_cols) ))
  args = [ meta[i] for i in meta_cols ]
  cursor=db.cursor()
  cursor.execute(insert,args)
  db.commit()

Donde meta es el diccionario que contiene los valores para insertar. La actualización se puede realizar de la misma forma:

  meta_cols=('SongName','SongArtist','SongAlbum','SongGenre')
  update='update Songs set {0} where id=%s'.
        .format(','.join([ '{0}=%s'.format(c) for c in meta_cols ]))
  args = [ meta[i] for i in meta_cols ]
  args.append( songid )
  cursor=db.cursor()
  cursor.execute(update,args)
  db.commit()
FDS
fuente
7
Estoy impresionado ... ¡lograste hacer ilegible el código Python!
Iharob Al Asimi
1

La primera solución funciona bien. Quiero agregar un pequeño detalle aquí. Asegúrese de que la variable que está intentando reemplazar / actualizar tendrá que ser de tipo str. Mi tipo de mysql es decimal pero tuve que hacer que la variable de parámetro fuera str para poder ejecutar la consulta.

temp = "100"
myCursor.execute("UPDATE testDB.UPS SET netAmount = %s WHERE auditSysNum = '42452'",(temp,))
myCursor.execute(var)
Parth Shah
fuente
0

Aquí tienes otra forma de hacerlo. Está documentado en el sitio web oficial de MySQL. https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-execute.html

En el espíritu, está usando la misma mecánica de la respuesta de @Trey Stout. Sin embargo, encuentro este más bonito y más legible.

insert_stmt = (
  "INSERT INTO employees (emp_no, first_name, last_name, hire_date) "
  "VALUES (%s, %s, %s, %s)"
)
data = (2, 'Jane', 'Doe', datetime.date(2012, 3, 23))
cursor.execute(insert_stmt, data)

Y para ilustrar mejor cualquier necesidad de variables:

NB: tenga en cuenta el escape que se está haciendo.

employee_id = 2
first_name = "Jane"
last_name = "Doe"

insert_stmt = (
  "INSERT INTO employees (emp_no, first_name, last_name, hire_date) "
  "VALUES (%s, %s, %s, %s)"
)
data = (employee_id, conn.escape_string(first_name), conn.escape_string(last_name), datetime.date(2012, 3, 23))
cursor.execute(insert_stmt, data)
Djidiouf
fuente