Implodieren einer Liste zur Verwendung in einer Python MySQLDB IN-Klausel

84

Ich weiß, wie man eine Liste einer Zeichenfolge zuordnet:

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

Und ich weiß, dass ich Folgendes verwenden kann, um diesen String in eine IN-Klausel zu bekommen:

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

Was ich brauche, ist das gleiche mit SASELD (Vermeidung von SQL-Injection) mit MySQLDB zu erreichen. Im obigen Beispiel ist Foostring anfällig, da es nicht als Argument für die Ausführung übergeben wird. Ich muss auch außerhalb der MySQL-Bibliothek zitieren und fliehen.

(Es gibt eine verwandte SO-Frage , aber die dort aufgeführten Antworten funktionieren entweder nicht für MySQLDB oder sind anfällig für SQL-Injection.)

mluebke
quelle
Sie können sich vielleicht von einer ähnlichen Frage inspirieren lassen, die in php stackoverflow.com/questions/327274/…
Zoredache
Mögliches Duplikat der Python-Liste in der SQL-Abfrage als Parameter
Kamil Sindi
@mluebke Haben Sie eine Idee zum Übergeben mehrerer Listen in Abfragen?
Dipen Dedania

Antworten:

154

Verwenden Sie die list_of_idsdirekt:

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

Auf diese Weise vermeiden Sie, dass Sie sich selbst zitieren müssen, und vermeiden alle Arten von SQL-Injektionen.

Beachten Sie, dass die Daten ( list_of_ids) als Parameter (nicht im Abfragetext) direkt an den MySQL-Treiber gesendet werden, sodass keine Injektion erfolgt. Sie können beliebige Zeichen in der Zeichenfolge belassen, ohne Zeichen entfernen oder zitieren zu müssen.

nosklo
quelle
2
@heikogerlach: Ich zitiere nicht die% s ... Die erste Zeile erstellt eine Zeichenfolge von "% s,% s,% s" ... der gleichen Größe von list_of_ids Länge.
Nosklo
Argh, du hast recht. Müssen härter schauen. Irgendwie habe ich es verwechselt. Gute Lösung.
Funktioniert das auch in SQLite? Weil ich es gerade ausprobiert habe und es auf Syntaxfehler hinweist.
Sohaib
@ Sohaib in SQLite ist das Ersatzzeichen ?nicht %sso, dass es funktionieren würde, wenn Sie die erste Zeile in ändern format_strings = ','.join('?' * len(list_of_ids)).
Nosklo
1
@kdas in Ihrem Fall möchten Sie nicht, dass das % format_stringsTeil die anderen %sPlatzhalter in Ihrer Abfrage ändert, sondern nur den IN (%s)Platzhalter. - Um dies zu erreichen, verdoppeln Sie alle %Zeichen außer dem, das Sie ersetzen möchten: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
3

Obwohl diese Frage ziemlich alt ist, dachte ich, es wäre besser, eine Antwort zu hinterlassen, falls jemand anderes nach dem sucht, was ich wollte

Akzeptierte Antworten werden unübersichtlich, wenn wir viele Parameter haben oder benannte Parameter verwenden möchten

Nach einigen Versuchen

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

Getestet mit python2.7,pymysql==0.7.11

markk
quelle
1
Dies funktioniert nicht mit Python 3 und MySQL-Connector-Python 8.0.21. Ein Fehler "Python-Tupel kann nicht in MySQL-Typ konvertiert werden" wird zurückgegeben.
Rubms
-1

Wenn Sie Django 2.0 or 2.1und verwenden Python 3.6, ist dies der richtige Weg:

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/de/2.1/topics/db/sql/#passing-parameters-into-raw

Belter
quelle
-1

Obwohl diese Frage ziemlich alt ist. Ich teile meine Lösung, wenn sie jemandem helfen kann.

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

Getestet mit Python=3.6

Aditya Sahu
quelle
Ich befürchte, diese Lösung ist anfällig für SQL-Injection-Angriffe, da die bereitgestellte Lösung list_to_checknicht SQL-Escape ist. Aus diesem Grund executeist es besser , die Werte als Parameter an zu übergeben . Verwenden Sie diese Lösung sehr vorsichtig (dh die Eingabe-IDs werden nicht als Parameter von außerhalb Ihrer Anwendung empfangen), da dies jemand verwenden könnte, um Ihr System anzugreifen und auf Ihre Datenbank zuzugreifen.
Rubms
-2

Eine weitere einfache Lösung mit Listenverständnis:

# 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
quelle
-4
list_of_ids = [ 1, 2, 3]
query = "select * from table where x in %s" % str(tuple(list_of_ids))
print query

Dies kann in einigen Anwendungsfällen funktionieren, wenn Sie sich nicht mit der Methode befassen möchten, bei der Sie Argumente übergeben müssen, um die Abfragezeichenfolge zu vervollständigen, und nur aufrufen möchten cursror.execute(query).

Ein anderer Weg könnte sein:

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

Sehr einfach: Verwenden Sie einfach die folgende Formation

rules_id = ["9", "10"]

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

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

Mizanur Rahman
quelle
Wo wird SQL zitiert und wird nicht ein Literal anstelle von Bindungsvariablen verwendet?
eckes
Nicht brauchen, es funktioniert einfach gut. Sie können testen, weil die Tupelbildung direkt als Zeichenfolge mit ersten Klammern ("9", "10") konvertiert wurde. Welche SQL-Formation anpassen. Sie brauchen also keine andere Formation, um SQL angrenzbar zu machen
Mizanur Rahman
1
und wenn ein rules_identhält "); DROP TABLES Bobby --?
eckes
Bereits gesagt "implodieren einer Liste" nicht ") ... also müssen Sie vor der Abfrage validieren
Mizanur Rahman
oder verwenden Sie: sql1 = "SELECT * FROM Anwesenheitsregeln_Staff WHERE ID in (" + "," .join (map (str, rules_id)) + ")"
Mizanur Rahman