implodere un elenco da utilizzare in una clausola Python MySQLDB IN


86

So come mappare un elenco a una stringa:

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

E so che posso usare quanto segue per inserire quella stringa in una clausola IN:

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

Quello di cui ho bisogno è realizzare la stessa cosa in modo SICURO (evitando l'iniezione SQL) usando MySQLDB. Nell'esempio precedente, poiché il foostring non viene passato come argomento da eseguire, è vulnerabile. Devo anche citare e scappare al di fuori della libreria mysql.

(C'è una domanda SO correlata , ma le risposte elencate qui o non funzionano per MySQLDB o sono vulnerabili all'iniezione SQL.)


Potresti essere in grado di trarre ispirazione da una domanda simile che viene fatta in php stackoverflow.com/questions/327274/…
Zoredache,


@mluebke Qualche idea sul passaggio di più elenchi nella query?
Dipen Dedania

Risposte:


161

Usa list_of_idsdirettamente:

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

In questo modo eviti di dover citare te stesso ed eviti tutti i tipi di iniezione sql.

Nota che data ( list_of_ids) va direttamente al driver di mysql, come parametro (non nel testo della query), quindi non c'è iniezione. Puoi lasciare tutti i caratteri che desideri nella stringa, senza bisogno di rimuovere o citare i caratteri.


2
@heikogerlach: Non sto citando% s ... La prima riga crea una stringa di "% s,% s,% s" ... la stessa dimensione della lunghezza di list_of_ids.
nosklo,

Argh, hai ragione. Hai bisogno di guardare più a fondo. In qualche modo l'ho confuso. Bella soluzione, però.

Funzionerà anche in sqlite? Perché l'ho appena provato e sembra indicare errori di sintassi.
Sohaib

@ Sohaib in sqlite il carattere sostitutivo ?non è %squindi funzionerebbe se si cambia la prima riga in format_strings = ','.join('?' * len(list_of_ids)).
nosklo

1
@kdas nel tuo caso non vuoi che la % format_stringsparte cambi gli altri %ssegnaposto nella tua query, solo il IN (%s)segnaposto - Il modo per ottenere questo è raddoppiare tutti i %caratteri tranne quello che vuoi sostituire: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

6

Sebbene questa domanda sia piuttosto vecchia, ho pensato che sarebbe stato meglio lasciare una risposta nel caso qualcun altro stesse cercando quello che volevo

La risposta accettata diventa confusa quando abbiamo molti parametri o se vogliamo usare parametri con nome

Dopo alcune prove

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

Testato con python2.7,pymysql==0.7.11


4
Questo non funziona con python 3 e mysql-connector-python 8.0.21. Viene restituito un errore "La tupla Python non può essere convertita in tipo MySQL".
Rubms

-2

Se usi Django 2.0 or 2.1e Python 3.6, questo è il modo giusto:

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

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


Se qualcosa non va, correggimi!
Belter

-2

Anche se questa domanda è piuttosto vecchia. Condivido la mia soluzione se può aiutare qualcuno.

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

Testato con Python=3.6


1
Temo che questa soluzione sia vulnerabile agli attacchi di SQL injection, poiché il fornito list_to_checknon è di escape SQL. Questo è il motivo per cui passare i valori come parametri a executeè più appropriato. Usa questa soluzione con molta attenzione (ovvero, gli ID di input non vengono ricevuti come parametri dall'esterno dell'applicazione), poiché qualcuno potrebbe usarla per attaccare il tuo sistema e accedere al tuo database.
Rubms

-3

Un'altra semplice soluzione che utilizza la comprensione delle liste:

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

-4
list_of_ids = [ 1, 2, 3]
query = "select * from table where x in %s" % str(tuple(list_of_ids))
print query

Questo potrebbe funzionare per alcuni casi d'uso se non desideri preoccuparti del metodo in cui devi passare argomenti per completare la stringa di query e desideri invocare solo cursror.execute(query).

Un altro modo potrebbe essere:

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

-7

Molto semplice: usa la formazione sottostante

rules_id = ["9", "10"]

sql1 = "SELECT * FROM attendance_rules_staff WHERE id in (" + "," .join (map (str, rules_id)) + ")"

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


Dove fa il quoting sql e non sta usando un letterale invece di bind variabili?
Suona il

Non è necessario, funziona semplicemente bene. Puoi testare la formazione della tupla perché convertita direttamente come stringa con le prime parentesi graffe ("9", "10"). Che regolano la formazione sql. Quindi non hai bisogno di altra formazione per rendere sql regolabile
Mizanur Rahman

1
e se un rules_idcontiene "); DROP TABLES Bobby --?
suona il

Già detto "implodere una lista" non ") ... quindi prima della query è necessario convalidare
Mizanur Rahman

oppure usa: sql1 = "SELECT * FROM attendance_rules_staff WHERE id in (" + "," .join (map (str, rules_id)) + ")"
Mizanur Rahman
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.