сжатие списка для использования в предложении python MySQLDB IN

86

Я знаю, как сопоставить список со строкой:

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

И я знаю, что могу использовать следующее, чтобы вставить эту строку в предложение IN:

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

Мне нужно сделать то же самое БЕЗОПАСНО (избегая SQL-инъекций) с помощью MySQLDB. В приведенном выше примере, поскольку строка foostring не передается в качестве аргумента для выполнения, она уязвима. Я также должен цитировать и избегать библиотеки mysql.

(Есть связанный вопрос SO , но перечисленные там ответы либо не работают для MySQLDB, либо уязвимы для SQL-инъекции.)

Mluebke
источник
Возможно, вы сможете почерпнуть вдохновение из аналогичного вопроса, который задается в php stackoverflow.com/questions/327274/…
Zoredache
Возможный дубликат списка Python в запросе sql в качестве параметра
Камил Синди
@mluebke Есть идеи о передаче нескольких списков в запросе?
Дипен Дедания

Ответы:

161

Используйте list_of_idsнапрямую:

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

Таким образом, вам не придется цитировать себя и избежать всех видов SQL-инъекций.

Обратите внимание, что data ( list_of_ids) передается непосредственно драйверу mysql в качестве параметра (не в тексте запроса), поэтому инъекции нет. Вы можете оставить любые символы в строке, не нужно удалять или заключать символы в кавычки.

носкло
источник
2
@heikogerlach: Я не цитирую% s ... Первая строка создает строку "% s,% s,% s" ... того же размера, что и длина list_of_ids.
nosklo
Ах, ты прав. Надо присмотреться. Как-то перепутала. Впрочем, хорошее решение.
Будет ли это работать и в sqlite? Потому что я просто попробовал и, кажется, указывает на синтаксические ошибки.
Sohaib
@Sohaib в sqlite заменяющий char ?не %sтак, что он будет работать, если вы измените первую строку на format_strings = ','.join('?' * len(list_of_ids)).
носкло 02
1
@kdas в вашем случае вы не хотите, чтобы % format_stringsчасть изменяла другие %sзаполнители в вашем запросе, только IN (%s)заполнитель - способ добиться этого - удвоить все %символы, кроме того, который вы хотите заменить: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

Хотя этот вопрос довольно старый, подумал, что лучше оставить ответ на случай, если кто-то еще искал то, что я хотел

Принятый ответ становится беспорядочным, когда у нас много параметров или если мы хотим использовать именованные параметры

После некоторых испытаний

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

Испытано с python2.7,pymysql==0.7.11

Markk
источник
4
Это не работает с python 3 и mysql-connector-python 8.0.21. Возвращается ошибка «Кортеж Python не может быть преобразован в тип MySQL».
Rubms
-2

Если вы используете Django 2.0 or 2.1и Python 3.6, это правильный путь:

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

ссылка: https://stackoverflow.com/a/23891759/2803344 https://docs.djangoproject.com/en/2.1/topics/db/sql/#passing-parameters-into-raw

Belter
источник
Если что не так, поправьте меня, пожалуйста!
Belter
-2

Хотя это довольно старый вопрос. Делюсь своим решением, если оно кому-то поможет.

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

Протестировано с Python=3.6

Адитья Саху
источник
1
Я боюсь, что это решение уязвимо для атак с использованием SQL-инъекций, поскольку предоставленное list_to_checkне экранировано с помощью SQL. Вот почему передача значений в качестве параметров executeболее уместна. Используйте это решение очень осторожно (то есть входные идентификаторы не принимаются как параметры извне вашего приложения), так как кто-то может использовать это для атаки на вашу систему и доступа к вашей базе данных.
Rubms
-3

Еще одно простое решение, использующее понимание списка:

# 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

Это может сработать для некоторых случаев использования, если вы не хотите беспокоиться о методе, в котором вы должны передавать аргументы для завершения строки запроса, и хотите просто вызвать cursror.execute(query).

Другой способ:

"select * from table where x in (%s)" % ', '.join(str(id) for id in list_of_ids)
Анураг Нилеш
источник
-7

Очень просто: используйте приведенную ниже схему.

rules_id = ["9", "10"]

sql1 = "ВЫБРАТЬ * FROM_rules_staff посещаемости, ГДЕ id в (" + "," .join (map (str, rules_id)) + ")"

"," .join (карта (str, rules_id))

Мизанур Рахман
источник
Где он цитирует sql и не использует ли он литерал вместо переменных связывания?
eckes
Не надо, просто нормально работает. Вы можете протестировать, потому что формирование кортежа напрямую преобразуется как строка с первыми фигурными скобками («9», «10»). Которые настраивают формирование sql. Таким образом, вам не нужно другое построение, чтобы сделать настраиваемым sql
Мизанур Рахман
1
а если rules_idсодержит "); DROP TABLES Bobby --?
eckes
Уже сказано «взрывать список» нельзя) ... так что перед запросом вам нужно проверить
Мизанур Рахман
или используйте: sql1 = "SELECT * FROM serveance_rules_staff WHERE id in (" + "," .join (map (str, rules_id)) + ")"
Мизанур Рахман