implosión de una lista para su uso en una cláusula de Python MySQLDB IN

84

Sé cómo asignar una lista a una cadena:

foostring = ",".join( map(str, list_of_ids) )

Y sé que puedo usar lo siguiente para incluir esa cadena en una cláusula IN:

cursor.execute("DELETE FROM foo.bar WHERE baz IN ('%s')" % (foostring))

Lo que necesito es lograr lo mismo de manera SEGURA (evitando la inyección de SQL) usando MySQLDB. En el ejemplo anterior, debido a que foostring no se pasa como argumento para ejecutar, es vulnerable. También tengo que citar y escapar fuera de la biblioteca mysql.

(Hay una pregunta SO relacionada , pero las respuestas enumeradas allí no funcionan para MySQLDB o son vulnerables a la inyección de SQL).

mluebke
fuente
Es posible que pueda inspirarse en una pregunta similar que se hace en php stackoverflow.com/questions/327274/…
Zoredache
Posible duplicado de la lista
Kamil Sindi
@mluebke ¿Alguna idea sobre cómo pasar varias listas en la consulta?
Dipen Dedania

Respuestas:

157

Utilice list_of_idsdirectamente:

format_strings = ','.join(['%s'] * len(list_of_ids))
cursor.execute("DELETE FROM foo.bar WHERE baz IN (%s)" % format_strings,
                tuple(list_of_ids))

De esa forma, evita tener que cotizar usted mismo y evita todo tipo de inyección SQL.

Tenga en cuenta que los datos ( list_of_ids) van directamente al controlador de mysql, como un parámetro (no en el texto de la consulta) por lo que no hay inyección. Puede dejar los caracteres que desee en la cadena, sin necesidad de eliminarlos o citarlos.

nosklo
fuente
2
@heikogerlach: No estoy citando% s ... La primera línea crea una cadena de "% s,% s,% s" ... el mismo tamaño de longitud de list_of_ids.
nosklo
Argh, tienes razón. Necesito mirar más duro. De alguna manera lo confundí. Aunque es una buena solución.
¿Funcionará esto también en sqlite? Porque lo acabo de probar y parece señalar errores de sintaxis.
Sohaib
@Sohaib en sqlite el carácter de reemplazo ?no es %sasí que funcionaría si cambia la primera línea a format_strings = ','.join('?' * len(list_of_ids)).
nosklo
1
@kdas en su caso, no desea que la % format_stringsparte cambie los otros %smarcadores de posición en su consulta, solo el IN (%s)marcador de posición: la forma de lograr esto es duplicar todos los %caracteres excepto el que desea reemplazar:query = ("select distinct cln from vcf_commits where branch like %%s and repository like %%s and filename in (%s) and author not like %%s" % format_strings,); cursor.execute(query, (branch, repository) + tuple(fname_list) + (invalid_author,))
nosklo
5

Aunque esta pregunta es bastante antigua, pensé que sería mejor dejar una respuesta en caso de que alguien más estuviera buscando lo que yo quería.

La respuesta aceptada se complica cuando tenemos muchos parámetros o si queremos usar parámetros con nombre

Después de algunas pruebas

ids = [5, 3, ...]  # list of ids
cursor.execute('''
SELECT 
...
WHERE
  id IN %(ids)s
  AND created_at > %(start_dt)s
''', {
  'ids': tuple(ids), 'start_dt': '2019-10-31 00:00:00'
})

Probado con python2.7,pymysql==0.7.11

Markk
fuente
3
Esto no funciona con python 3 y mysql-connector-python 8.0.21. Se devuelve un error "La tupla de Python no se puede convertir al tipo MySQL".
Rubms
-1

Si usa Django 2.0 or 2.1y Python 3.6, esta es la manera correcta:

from django.db import connection
RESULT_COLS = ['col1', 'col2', 'col3']
RESULT_COLS_STR = ', '.join(['a.'+'`'+i+'`' for i in RESULT_COLS])
QUERY_INDEX = RESULT_COLS[0]

TABLE_NAME = 'test'
search_value = ['ab', 'cd', 'ef']  # <-- a list
query = (
    f'SELECT DISTINCT {RESULT_COLS_STR} FROM {TABLE_NAME} a '
    f'WHERE a.`{RESULT_COLS[0]}` IN %s '
    f'ORDER BY a.`{RESULT_COLS[0]}`;'
)  # <- 'SELECT DISTINCT a.`col1`, a.`col2`, a.`col3` FROM test a WHERE a.`col1` IN %s ORDER BY a.`col1`;'
with connection.cursor() as cursor:
    cursor.execute(query, params=[search_value])  # params is a list with a list as its element

ref: https://stackoverflow.com/a/23891759/2803344 https://docs.djangoproject.com/en/2.1/topics/db/sql/#passing-parameters-into-raw

Belter
fuente
-1

Aunque esta pregunta es bastante antigua. Estoy compartiendo mi solución si puede ayudar a alguien.

list_to_check = ['A', 'B'] cursor.execute("DELETE FROM foo.bar WHERE baz IN ({})".format(str(list_to_check)[1:-1])

Probado con Python=3.6

Aditya Sahu
fuente
Me temo que esta solución es vulnerable a los ataques de inyección de SQL, ya que el proporcionado list_to_checkno se escapa de SQL. Es por eso que pasar los valores como parámetros a executees más apropiado. Utilice esta solución con mucho cuidado (es decir, los ID de entrada no se reciben como parámetros desde el exterior de su aplicación), ya que alguien podría usar esto para atacar su sistema y acceder a su base de datos.
Rubms
-2

Otra solución simple que usa la comprensión de listas:

# creating a new list of strings and convert to tuple
sql_list = tuple([ key.encode("UTF-8") for key in list_of_ids ])

# replace "{}" with "('id1','id2',...'idlast')"
cursor.execute("DELETE FROM foo.bar WHERE baz IN {}".format(sql_list))
chenchuk
fuente
-4
list_of_ids = [ 1, 2, 3]
query = "select * from table where x in %s" % str(tuple(list_of_ids))
print query

Esto podría funcionar para algunos casos de uso si no desea preocuparse por el método en el que tiene que pasar argumentos para completar la cadena de consulta y desea invocar solo cursror.execute(query) .

Otra forma podría ser:

"select * from table where x in (%s)" % ', '.join(str(id) for id in list_of_ids)
Anurag Nilesh
fuente
-7

Muy simple: solo usa la siguiente formación

rules_id = ["9", "10"]

sql1 = "SELECCIONAR * DEL PERSONAL DE REGLAS DE ASISTENCIA DONDE id en (" + "," .join (map (str, id_reglas)) + ")"

"," .join (mapa (str, rules_id))

Mizanur Rahman
fuente
¿Dónde hace las citas SQL y no se usa un literal en lugar de variables de enlace?
eckes
No es necesario, simplemente funciona bien. Puede probar Porque la formación de tuplas se convierte directamente como cadena con las primeras llaves ("9", "10"). Que ajustan la formación de sql. Por lo tanto, no necesita otra formación para hacer sql adjastable
Mizanur Rahman
1
y si rules_idcontiene "); DROP TABLES Bobby --?
eckes
Ya se le dijo "implosión de una lista" no ") ... así que antes de la consulta debe validar
Mizanur Rahman
o use: sql1 = "SELECT * FROM assist_rules_staff WHERE id in (" + "," .join (map (str, rules_id)) + ")"
Mizanur Rahman