Как использовать переменные в операторе SQL в Python?

#python #database #sqlite

Вопрос:

У меня есть следующий код на Python:

 cursor.execute("INSERT INTO table VALUES var1, var2, var3,")
 

где var1 -целое число, var2 а var3 -строки.

Как я могу написать имена переменных без включения их в Python как части текста запроса?

Ответ №1:

 cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))
 

Обратите внимание, что параметры передаются в виде кортежа.

API базы данных выполняет правильное экранирование и кавычки переменных. Будьте осторожны, не используйте оператор форматирования строк ( % ), потому что

  1. он не делает никаких побегов или цитирования.
  2. он подвержен неконтролируемым атакам в строковом формате, например, инъекциям SQL.

Комментарии:

1. Интересно, почему он работает с переменными по отдельности,а не в массиве (var1,var2, var3)?

2. Согласно спецификациям API БД, похоже, что это может быть в любом случае: python.org/dev/peps/pep-0249

3. @thekashyap Прочитайте еще раз внимательно. Что небезопасно, так это использование оператора форматирования строк % . На самом деле, я так и говорю в ответе.

4. Отклонено, потому что в ответе сказано не использовать % , но использует его три раза. Было бы здорово получить дополнительные объяснения.

5.@eric в ответе говорится, что не используйте % оператор для форматирования строки. Те % , что в строке, используются cursor.execute напрямую, и поскольку он знает, что генерирует SQL, он может сделать больше для вашей защиты.

Ответ №2:

Различным реализациям DB-API Python разрешено использовать разные заполнители, поэтому вам нужно будет выяснить, какой из них вы используете-это может быть (например, с MySQLdb):

 cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))
 

или (например, с помощью sqlite3 из стандартной библиотеки Python):

 cursor.execute("INSERT INTO table VALUES (?, ?, ?)", (var1, var2, var3))
 

или еще другие (после VALUES того , как вы могли бы (:1, :2, :3) , или «именованные стили» (:fee, :fie, :fo) , или (%(fee)s, %(fie)s, %(fo)s) где вы передаете дикт вместо карты в качестве второго аргумента execute ). Проверьте paramstyle строковую константу в используемом вами модуле DB API и найдите параметр paramstyle по адресу http://www.python.org/dev/peps/pep-0249/ чтобы увидеть, каковы все стили передачи параметров!

Комментарии:

1. Можно ли сделать то же самое, но с помощью внешнего сценария SQL?

Ответ №3:

Многими способами. НЕ используйте самый очевидный ( %s с % ) в реальном коде, он открыт для атак.

Здесь скопируйте и вставьте из pydoc из sqlite3:

 # Never do this -- insecure!
symbol = 'RHAT'
c.execute("SELECT * FROM stocks WHERE symbol = '%s'" % symbol)

# Do this instead
t = ('RHAT',)
c.execute('SELECT * FROM stocks WHERE symbol=?', t)
print c.fetchone()

# Larger example that inserts many records at a time
purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
             ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
             ('2006-04-06', 'SELL', 'IBM', 500, 53.00),
            ]
c.executemany('INSERT INTO stocks VALUES (?,?,?,?,?)', purchases)
 

Больше примеров, если вам нужно:

 # Multiple values single statement/execution
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', ('RHAT', 'MSO'))
print c.fetchall()
c.execute('SELECT * FROM stocks WHERE symbol IN (?, ?)', ('RHAT', 'MSO'))
print c.fetchall()
# This also works, though ones above are better as a habit as it's inline with syntax of executemany().. but your choice.
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', 'RHAT', 'MSO')
print c.fetchall()
# Insert a single item
c.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', ('2006-03-28', 'BUY', 'IBM', 1000, 45.00))
 

Комментарии:

1. Некоторые реализации DB-API фактически используют %s для своих переменных-особенно psycopg2 для PostgreSQL. Это не следует путать (хотя это легко) с использованием %s с оператором % для замены строки. Я был бы очень рад, если бы для переносимости у нас был просто определенный стандартный способ указания параметров SQL для DB-API.

Ответ №4:

http://www.amk.ca/python/writing/DB-API.html

Будьте осторожны, когда вы просто добавляете значения переменных в свои операторы: представьте, что пользователь называет себя ';DROP TABLE Users;' -вот почему вам нужно использовать экранирование SQL, которое предоставляет Python, когда вы используете cursor.execute его достойным образом. Пример в URL-адресе::

 cursor.execute("insert into Attendees values (?, ?, ?)", (name, seminar, paid))
 

Комментарии:

1. На самом деле, это не побег SQL. Это привязка к переменной, которая намного проще и более прямая. Значения привязываются к оператору SQL после синтаксического анализа, что делает его невосприимчивым к любой инъекционной атаке.

2. ну, будет ли это экранирование SQL или привязка переменных, зависит от того, насколько хорош или плох ваш драйвер сервера базы данных / DB-API. Я видел некоторые реальные, широко развернутые производственные базы данных, в которых драйвер DB-API просто ускользает, вместо того, чтобы хранить данные и код вне диапазона на проводе. Излишне говорить, что я не очень уважаю эти так называемые «базы данных».

Ответ №5:

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

Учитывая запрос

 INSERT INTO mytable (fruit) VALUES (%s)
 

Как правило*, передаваемое значение cursor.execute все равно должно быть кортежем, даже если оно является одноэлементным, поэтому мы должны предоставить кортеж из одного элемента, например: (value,) .

 cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple',))
 

Передача одной строки

 cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple'))
 

приведет к ошибке, которая зависит от соединителя DB-API, например

  • psycopg2:

    Ошибка типа: не все аргументы преобразуются во время форматирования строки

  • sqlite3

    sqlite3.Ошибка программирования: Неверное количество предоставленных привязок. В текущем заявлении используется 1, и в нем указано 5

  • mysql.коннектор

    mysql.connector.ошибки.Ошибка программирования: 1064 (42000): У вас ошибка в синтаксисе SQL;


* Соединитель pymysql обрабатывает один строковый параметр без ошибок. Однако лучше обернуть строку в кортеж, даже если это один, потому что

  • вам не нужно будет менять код, если вы переключите пакет соединителя
  • вы сохраняете согласованную мысленную модель параметров запроса, представляющую собой последовательность объектов, а не один объект.