Упити са параметрима¶
Често се јавља потреба да упит зависи од уноса корисника. На пример, наредни програм исписује све ученике чије је презиме и чији је разред једнак ономе што је корисник унео.
import os
import sqlite3
prezime = input("Унеси презиме: ")
razred = int(input("Унеси разред: "))
db_conn = sqlite3.connect(os.path.join(os.getcwd(), 'dnevnik.db'))
cur = db_conn.cursor()
res = cur.execute("SELECT ime, prezime FROM ucenik WHERE prezime=? AND razred=?",
(prezime, razred))
for ime, prezime in res:
print(ime, prezime)
db_conn.close()
На почетку скрипта корисник уноси презиме које се смешта у променљиву
prezime
и разред који се смешта у променљиву razred
. Упит који
се формира садржи два параметра означена упитницима: "SELECT ime
FROM ucenik WHERE prezime=? AND razred=?"
. Метода execute
након
упита прима вредности тих параметара (у облику уређене торке) и
параметри се аутоматски замењују наместо упитника, водећи рачуна о
коректности и безбедности.
Још један начин да се постигне исти ефекат је да се користе именовани параметри (уместо безимених).
res = cur.execute("SELECT ime, prezime FROM ucenik WHERE prezime=:prezime AND razred=:razred",
{"prezime": prezime, "razred": razred});
У овом случају се уместо упитника наводе називи параметара (у овом
случају то су називи :prezime
и :razred
), док се вредности
параметара не наводе у облику уређене торке, већ у облику речника који
називе параметара пресликава у њихове вредности.
Ако постоји само један безимени параметар, тада се вредност назива у
оквиру „уређене једнорке”, што звучи помало апсурдно, а и ствара
одређене проблеме. Наиме, ако се нека вредност наведе у заградама, она
се не сматра елементом „једнорке”, већ се заграде просто
занемарују. Да би се нагласило да је једна вредност део торке,
потребно је навести је у наредном облику (vrednost, )
. На пример,
ако желимо да вршимо селекцију само на основу унетог презимена, то се
може урадити на следећи начин.
res = cur.execute("SELECT ime, prezime FROM ucenik WHERE prezime=?", (prezime,))
Важно. Могло би се помислити да би замена параметара у упиту могла да се изврши коришћењем механизама за баратање нискама у језику Python. На пример, могло би се урадити овако нешто.
res = cur.execute("SELECT ime, prezime FROM ucenik WHERE prezime='" + prezime + "'")
Иако ово ради исправно, оно представља велики безбедносни пропуст
(познат под називом SQL injection). Наиме, корисник вашег Python
програма може уместо презимена да унесе и неку ниску која садржи и SQL кôд. На пример
' OR True; --
. Тиме се добија наредни упит:
SELECT ime, prezime FROM ucenik WHERE prezime='' OR True; --'
Извршавањем упита добија се следећи резултат:
ime |
prezime |
---|---|
Петар |
Петровић |
Милица |
Јовановић |
Лидија |
Петровић |
Петар |
Миловановић |
Ана |
Пекић |
… |
… |
То значи да су приказана имена свих ученика из табеле, чиме се можда
откривају неки поверљиви подаци. Зашто ово ради? Па услов OR True
је увек тачан, па се на овај начин заправо не врши никаква
рестрикција. Коментари у језику SQL се наводе након две цртице (све
након --
се сматра коментаром), па су две цртице додате да би
неутралисале завршни апостроф.
Због свега наведеног, параметризовани упити би увек требало да буду
реализовани коришћењем подршке коју метода execute
пружа (безимени
или именовани параметри).
Више упита¶
Понекад је потребно извршити више упита који се разликују само по
параметрима. У том случају је могуће позвати методу executemany
и
проследити јој листу торки које чувају различите вредности параметара.
На пример, наредни скрипт убацује у табелу податке о неколико ученика.
import os
import sqlite3
ucenici = [ ("Јована", "Ђорђевић", 3, 1),
("Мајда", "Зорановић", 2, 3),
("Лазар", "Табаковић", 4, 1),
("Мирна", "Ђерић", 3, 3) ]
db_conn = sqlite3.connect(os.path.join(os.getcwd(), 'dnevnik.db'))
cur = db_conn.cursor()
cur.executemany("INSERT INTO ucenik (ime, prezime, razred, odeljenje)" +
"VALUES (?, ?, ?, ?)", ucenici)
db_conn.commit()
db_conn.close()