implodowanie listy do użycia w klauzuli MySQLDB IN języka Python

84

Wiem, jak zmapować listę na ciąg:

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

Wiem, że mogę użyć następującego ciągu, aby umieścić ten ciąg w klauzuli IN:

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

To, czego potrzebuję, to zrobić to samo BEZPIECZNIE (unikając wstrzykiwania SQL) za pomocą MySQLDB. W powyższym przykładzie, ponieważ foostring nie jest przekazywany jako argument do wykonania, jest podatny na ataki. Muszę też cytować i uciec poza bibliotekę mysql.

(Istnieje powiązane pytanie SO , ale wymienione tam odpowiedzi albo nie działają w przypadku MySQLDB, albo są podatne na wstrzyknięcie SQL).

mluebke
źródło
Być może uda ci się uzyskać inspirację z podobnego pytania, które jest zrobione na php stackoverflow.com/questions/327274/ ...
Zoredache
Możliwy duplikat listy Pythona w kwerendzie sql jako parametr
Kamil Sindi
@mluebke Masz pomysł na przekazywanie wielu list w zapytaniu?
Dipen Dedania

Odpowiedzi:

158

Użyj list_of_idsbezpośrednio:

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

W ten sposób unikniesz konieczności cytowania siebie i wszelkiego rodzaju iniekcji sql.

Zauważ, że data ( list_of_ids) trafia bezpośrednio do sterownika mysql jako parametr (nie w tekście zapytania), więc nie ma iniekcji. Możesz zostawić dowolne znaki w ciągu znaków, bez potrzeby usuwania lub cytowania znaków.

nosklo
źródło
2
@heikogerlach: Nie cytuję% s ... Pierwsza linia tworzy ciąg "% s,% s,% s" ... o tej samej długości list_of_ids.
nosklo
Argh, masz rację. Muszę spojrzeć mocniej. Jakoś to pomieszałem. Ale fajne rozwiązanie.
Czy to zadziała również w sqlite? Ponieważ właśnie go wypróbowałem i wydaje się, że wskazuje błędy składniowe.
Sohaib,
@Sohaib w sqlite znak zastępczy ?nie jest, %swięc zadziała, jeśli zmienisz pierwszą linię na format_strings = ','.join('?' * len(list_of_ids)).
nosklo
1
@kdas w twoim przypadku nie chcesz, aby % format_stringsczęść zmieniała inne %ssymbole zastępcze w zapytaniu, tylko IN (%s)symbol zastępczy - sposobem na to jest podwojenie wszystkich %znaków oprócz tego, który chcesz zastąpić: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

Chociaż to pytanie jest dość stare, pomyślałem, że lepiej zostawić odpowiedź na wypadek, gdyby ktoś szukał tego, czego szukałem

Zaakceptowana odpowiedź staje się nieczytelna, gdy mamy dużo parametrów lub gdy chcemy używać nazwanych parametrów

Po kilku próbach

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

Testowane python2.7,pymysql==0.7.11

markk
źródło
3
To nie działa z Python 3 i mysql-connector-python 8.0.21. Zwracany jest błąd „Nie można przekonwertować krotki Pythona na typ MySQL”.
Rubms
-1

Jeśli używasz Django 2.0 or 2.1i Python 3.6, to jest właściwy sposób:

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

Pasować
źródło
-1

Chociaż to pytanie jest dość stare. Dzielę się moim rozwiązaniem, jeśli może to komuś pomóc.

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

Testowane z Python=3.6

Aditya Sahu
źródło
Obawiam się, że to rozwiązanie jest podatne na ataki typu SQL injection, ponieważ dostarczone list_to_checknie jest zabezpieczone przed ucieczką SQL. Dlatego executewłaściwsze jest przekazywanie wartości jako parametrów do . Korzystaj z tego rozwiązania bardzo ostrożnie (tzn. Identyfikatory wejściowe nie są odbierane jako parametry z zewnątrz aplikacji), ponieważ ktoś mógłby użyć tego rozwiązania do zaatakowania systemu i uzyskania dostępu do bazy danych.
Rubms
-2

Kolejne proste rozwiązanie wykorzystujące rozumienie list:

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

Może to zadziałać w niektórych przypadkach użycia, jeśli nie chcesz zajmować się metodą, w której musisz przekazać argumenty, aby uzupełnić ciąg zapytania i chciałbyś tylko wywołać cursror.execute(query).

Innym sposobem może być:

"select * from table where x in (%s)" % ', '.join(str(id) for id in list_of_ids)
Anurag Nilesh
źródło
-7

Bardzo proste: po prostu użyj poniższej formacji

rules_id = ["9", "10"]

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

„,” .join (map (str, rules_id))

Mizanur Rahman
źródło
Gdzie robi cytowanie sql i czy nie używa to literału zamiast zmiennych wiążących?
eckes
Nie trzeba, po prostu działa dobrze. Możesz przetestować, ponieważ tworzenie krotki jest bezpośrednio konwertowane na ciąg z pierwszymi nawiasami klamrowymi („9”, „10”). Które dostosowują formację sql. Więc nie potrzebujesz innej formacji do dostosowania sql
Mizanur Rahman
1
a jeśli rules_idzawiera "); DROP TABLES Bobby --?
eckes
Powiedziałem już „imploding a list” nie) ... więc przed zapytaniem musisz zweryfikować
Mizanur Rahman
lub użyj: sql1 = "SELECT * FROM attance_rules_staff WHERE id in (" + "," .join (map (str, rules_id)) + ")"
Mizanur Rahman