Параметризованные запросы MySQL

80

Мне сложно использовать модуль MySQLdb для вставки информации в мою базу данных. Мне нужно вставить в таблицу 6 переменных.

cursor.execute ("""
    INSERT INTO Songs (SongName, SongArtist, SongAlbum, SongGenre, SongLength, SongLocation)
    VALUES
        (var1, var2, var3, var4, var5, var6)

""")

Может ли кто-нибудь помочь мне с синтаксисом здесь?

Specto
источник

Ответы:

262

Остерегайтесь использования строковой интерполяции для SQL-запросов, поскольку она не сможет правильно избежать входных параметров и оставит ваше приложение открытым для уязвимостей SQL-инъекций. Разница может показаться незначительной, но на самом деле она огромна .

Неверно (с проблемами безопасности)

c.execute("SELECT * FROM foo WHERE bar = %s AND baz = %s" % (param1, param2))

Правильно (с экранированием)

c.execute("SELECT * FROM foo WHERE bar = %s AND baz = %s", (param1, param2))

Это добавляет путаницы, что модификаторы, используемые для привязки параметров в операторе SQL, различаются между различными реализациями API БД и что клиентская библиотека mysql использует printfсинтаксис стиля вместо более общепринятого символа '?' маркер (используется, например, python-sqlite).

Эмиль Х
источник
3
@Specto IMO имеет смысл всегда придерживаться правильных и безопасных способов реализации. Это создает правильные привычки и хорошую культуру программирования. Также никто не знает, как ваш код будет использоваться в будущем; кто-то может использовать его позже для другой системы или веб-сайта.
Роман Подлинов
@BryanHunt Можно включить использование? с аргументом где-то, но это не приветствуется, потому что он мало говорит вам о том, какой аргумент и где идет. (То же самое, конечно, можно сказать и о% s, что не рекомендуется по той же причине.) Подробнее здесь: python.org/dev/peps/pep-0249/#paramstyle
kqr
1
Исходя из php / pdo, я был очень запутан, если маркер printfстиля %s, и я был в некотором роде напуган тем, что писал уязвимые запросы. Спасибо, что сняли беспокойство! :)
Дарра Энрайт
18
Если это единственный параметр, не забудьте оставить запятую: c.execute("SELECT * FROM foo WHERE bar = %s", (param1,))
Marius Lian
1
Для контекста в параметрах выполнения курсора (и почему% s все еще необходим) ссылка api для курсора MySQLdb находится по адресу mysql-python.sourceforge.net/MySQLdb-1.2.2/public/…
RedBarron
50

У вас есть несколько вариантов. Вы захотите освоиться с итерацией строк в Python. Этот термин вы, возможно, добьетесь большего успеха в поисках в будущем, когда захотите узнать о таких вещах.

Лучше для запросов:

some_dictionary_with_the_data = {
    'name': 'awesome song',
    'artist': 'some band',
    etc...
}
cursor.execute ("""
            INSERT INTO Songs (SongName, SongArtist, SongAlbum, SongGenre, SongLength, SongLocation)
            VALUES
                (%(name)s, %(artist)s, %(album)s, %(genre)s, %(length)s, %(location)s)

        """, some_dictionary_with_the_data)

Учитывая, что у вас, вероятно, уже есть все данные в объекте или словаре, вам больше подойдет второй формат. Также отстойно подсчитывать появления "% s" в строке, когда вам нужно вернуться и обновить этот метод через год :)

Трей Стаут
источник
2
Похоже, словарный подход работает лучше в случае, если данная переменная привязки должна использоваться более чем в одном месте в операторе SQL. При позиционном подходе нам нужно передавать переменную столько раз, сколько на нее ссылаются, что не очень желательно.
codeforester
15

В связанных документах приводится следующий пример:

   cursor.execute ("""
         UPDATE animal SET name = %s
         WHERE name = %s
       """, ("snake", "turtle"))
   print "Number of rows updated: %d" % cursor.rowcount

Так что вам просто нужно адаптировать это к своему собственному коду - пример:

cursor.execute ("""
            INSERT INTO Songs (SongName, SongArtist, SongAlbum, SongGenre, SongLength, SongLocation)
            VALUES
                (%s, %s, %s, %s, %s, %s)

        """, (var1, var2, var3, var4, var5, var6))

(Если SongLength является числовым, вам может потребоваться использовать% d вместо% s).

Марсель Гусман
источник
1
будет ли это работать, если в переменных var1 и var2 есть символы типа "или".
sheki
3
AFAIK вы должны использовать %sтам независимо от типа. @sheki: Да
ThiefMaster
7

Фактически, даже если ваша переменная (SongLength) числовая, вам все равно придется отформатировать ее с помощью% s, чтобы правильно привязать параметр. Если вы попытаетесь использовать% d, вы получите сообщение об ошибке. Вот небольшой отрывок из этой ссылки http://mysql-python.sourceforge.net/MySQLdb.html :

Для выполнения запроса вам сначала понадобится курсор, а затем вы можете выполнять по нему запросы:

c=db.cursor()
max_price=5
c.execute("""SELECT spam, eggs, sausage FROM breakfast
          WHERE price < %s""", (max_price,))

В этом примере max_price = 5 Почему же тогда использовать% s в строке? Поскольку MySQLdb преобразует его в буквальное значение SQL, которое представляет собой строку «5». Когда он закончится, запрос фактически скажет: «... WHERE price <5».

daSong
источник
Да, это странно, хорошо ... вы думаете, что формат "printf" будет означать ... на самом деле формат printf, а не просто использование% s повсюду.
fthinker
6

В качестве альтернативы выбранному ответу и с той же безопасной семантикой, что и у Марселя, вот компактный способ использования словаря Python для указания значений. Его преимущество заключается в том, что его легко изменять при добавлении или удалении столбцов для вставки:

  meta_cols=('SongName','SongArtist','SongAlbum','SongGenre')
  insert='insert into Songs ({0}) values ({1})'.
        .format(','.join(meta_cols), ','.join( ['%s']*len(meta_cols) ))
  args = [ meta[i] for i in meta_cols ]
  cursor=db.cursor()
  cursor.execute(insert,args)
  db.commit()

Где мета - словарь, содержащий значения для вставки. Обновление можно произвести аналогично:

  meta_cols=('SongName','SongArtist','SongAlbum','SongGenre')
  update='update Songs set {0} where id=%s'.
        .format(','.join([ '{0}=%s'.format(c) for c in meta_cols ]))
  args = [ meta[i] for i in meta_cols ]
  args.append( songid )
  cursor=db.cursor()
  cursor.execute(update,args)
  db.commit()
FDS
источник
7
Я впечатлен ... вам удалось сделать код Python нечитаемым!
Iharob Al Asimi
1

Первое решение работает хорошо. Я хочу добавить сюда одну небольшую деталь. Убедитесь, что переменная, которую вы пытаетесь заменить / обновить, должна иметь тип str. Мой тип mysql является десятичным, но мне пришлось сделать переменную параметра как str, чтобы иметь возможность выполнить запрос.

temp = "100"
myCursor.execute("UPDATE testDB.UPS SET netAmount = %s WHERE auditSysNum = '42452'",(temp,))
myCursor.execute(var)
Парт Шах
источник
0

Вот еще один способ сделать это. Это задокументировано на официальном сайте MySQL. https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-execute.html

По духу он использует ту же механику, что и ответ @Trey Stout. Однако я нахожу этот более красивым и более читаемым.

insert_stmt = (
  "INSERT INTO employees (emp_no, first_name, last_name, hire_date) "
  "VALUES (%s, %s, %s, %s)"
)
data = (2, 'Jane', 'Doe', datetime.date(2012, 3, 23))
cursor.execute(insert_stmt, data)

И чтобы лучше проиллюстрировать необходимость в переменных:

NB: обратите внимание на совершаемый побег.

employee_id = 2
first_name = "Jane"
last_name = "Doe"

insert_stmt = (
  "INSERT INTO employees (emp_no, first_name, last_name, hire_date) "
  "VALUES (%s, %s, %s, %s)"
)
data = (employee_id, conn.escape_string(first_name), conn.escape_string(last_name), datetime.date(2012, 3, 23))
cursor.execute(insert_stmt, data)
Джидиуф
источник