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


80

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

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

""")

Хтось може мені тут допомогти з синтаксисом?

Відповіді:


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 DB і що клієнтська бібліотека mysql використовує printfсинтаксис стилю замість загальноприйнятого '?' маркер (використовується напр. python-sqlite).


3
@Specto IMO має сенс завжди дотримуватися правильних та безпечних способів впровадження. Це створює правильні звички та хорошу культуру програмування. Також ніхто не знає, як ваш код буде використовуватися в майбутньому; хтось може використовувати його пізніше для іншої системи або веб-сайту.
Роман Подлінов

@BryanHunt Ви можете ввімкнути використання? з аргументом десь, але це не рекомендується, оскільки це не говорить вам багато про те, який аргумент куди йде. (Те саме можна сказати про% s, звичайно, що не рекомендується з тієї ж причини.) Більше інформації тут: python.org/dev/peps/pep-0249/#paramstyle
kqr

1
Виходячи з php / pdo, я був дуже заплутаний, якщо маркер printfстилю %s, і я був переляканий тим, що писав вразливі запити. Дякуємо, що прояснили це занепокоєння! :)
Darragh Enright,

18
Коли це єдиний параметр, пам’ятайте дотримуватися коми: c.execute("SELECT * FROM foo WHERE bar = %s", (param1,))
Маріус Ліан

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". Після завершення запиту насправді буде сказано: "... ДЕ де ціна <5".


Так, це дивно, добре ... ви думаєте, що формат "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()

7
Я вражений ... вам вдалося зробити код python нечитабельним!
Іхароб Аль Асімі,

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)

А щоб краще проілюструвати будь-яку потребу у змінних:

Примітка: зверніть увагу на втечу, що робиться.

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.