Imploding a list for use in a Python MySQL IN clause
Создание списка для использования в предложении Python MySQL IN
Я знаю, как сопоставить список со строкой:
foostring = ",".join( map(str, list_of_ids) )
И я знаю, что могу использовать следующее, чтобы вставить эту строку в предложение IN:
cursor.execute("DELETE FROM foo.bar WHERE baz IN ('%s')" % (foostring))
Как я могу сделать то же самое безопасно (избегая SQL-инъекции), используя базу данных MySQL?
В приведенном выше примере, поскольку foostring не передается в качестве аргумента для выполнения, он уязвим. Мне также приходится заключать его в кавычки и экранировать за пределами библиотеки MySQL.
(Есть связанный с Stack Overflow вопрос, но ответы, перечисленные там, либо не работают для базы данных MySQL, либо уязвимы для SQL-инъекции.)
Переведено автоматически
Ответ 1
Используйте 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-инъекций.
Обратите внимание, что данные (list_of_ids) передаются непосредственно в драйвер MySQL в качестве параметра (не в тексте запроса), поэтому никакого внедрения не происходит. Вы можете оставить в строке любые символы, которые хотите; нет никакой необходимости удалять символы или заключать их в кавычки.
Ответ 2
Принятый ответ становится беспорядочным, когда у нас много параметров или если мы хотим использовать именованные параметры.
После некоторых испытаний,
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' })
Он был протестирован с Python 2.7 и pymysql 0.7.11.
Ответ 3
Похоже, что это все еще остается проблемой с Python3 в 2021 году, как указано в комментарии Rubms к ответу markk.
Добавление около 9 строк кода к методу "_process_params_dict" в "cursor.py" в пакете mysql connector для обработки кортежей решило проблему для меня:
def_process_params_dict(self, params): """Process query parameters given as dictionary""" try: to_mysql = self._connection.converter.to_mysql escape = self._connection.converter.escape quote = self._connection.converter.quote res = {} for key, value inlist(params.items()): iftype(value) istuple: ### BEGIN MY ADDITIONS res[key.encode()] = b'' for subvalue in value: conv = subvalue conv = to_mysql(conv) conv = escape(conv) conv = quote(conv) res[key.encode()] = res[key.encode()] + b',' + conv iflen(res[key.encode()]) else conv else: ### END MY ADDITIONS conv = value conv = to_mysql(conv) conv = escape(conv) conv = quote(conv) res[key.encode()] = conv except Exception as err: raise errors.ProgrammingError( "Failed processing pyformat-parameters; %s" % err) else: return res
Ответ 4
Возможно, я немного опоздал с ответом на вопрос, но я наткнулся на похожую проблему, но я хотел использовать dict именованных параметров вместо кортежа (потому что, если я хочу изменить параметры, чтобы добавить или удалить некоторые, я не хочу переконструировать кортеж, перепутать порядок может быть очень легко и привести к ошибкам ...).
Мое решение заключалось в форматировании строки запроса, чтобы разбить параметр на несколько параметров, а затем сконструировать параметр dict с этими новыми параметрами:
from typing import Iterable
query = """ SELECT * FROM table WHERE id IN (%(test_param)s) """
parameters = {"test_param": [1, 2, 3])
new_params = {}
for k, v in parameters.items(): ifisinstance(v, Iterable): iterable_params = {f"{k}_{i}": value for i, value inenumerate(v)} iterable_params_formatted = [f"%({k}_{i})s"for i inrange(0, len(v))] query = query.replace(f"%({k})s", ", ".join(iterable_params_formatted)) new_params.update(iterable_params) else: new_params[k] = v
print(query) print(new_params)
Результат:
> SELECT * FROM table WHERE id IN (%(test_param_0)s, %(test_param_1)s, %(test_param_2)s)