În sistemele de management al bazelor de date, o interogare pregătită sau o interogare parametrizată este capacitatea unui SGBD de a precompila cod SQL separat de date [1] . Avantajele interogărilor pregătite:
O instrucțiune pregătită este de fapt un șablon precompilat care este înlocuit cu valori constante în timpul fiecărei execuții și este obișnuit să se utilizeze instrucțiuni SQL DML precum INSERT , SELECT sau UPDATE .
Secvența obișnuită pentru utilizarea instrucțiunilor pregătite este:
O alternativă la o interogare pregătită este apelarea SQL direct din codul sursă al aplicației într-un mod care combină codul și datele. Echivalent direct cu exemplul de mai sus:
INSERT INTO produse (nume, preț) VALORI ("bicicletă", "10900");Nu toate optimizările pot fi efectuate în momentul compilării șablonului de instrucțiuni din două motive: cel mai bun plan de interogare poate depinde de valorile anumitor parametri, iar cel mai bun plan de interogare se poate schimba în timp din cauza modificării tabelelor și indicilor [4] . Când și dacă o interogare pregătită este executată o singură dată, aceasta va rula mai lent din cauza călătoriei suplimentare dus-întors către server [5] . Limitările de implementare pot duce, de asemenea, la degradarea performanței; de exemplu, unele versiuni de MySQL nu au memorat în cache rezultatele interogărilor pregătite [6] . Procedurile stocate , care sunt, de asemenea, precompilate și stocate pe server pentru a fi executate ulterioare, oferă beneficii similare. Spre deosebire de procedurile stocate, o interogare pregătită nu este de obicei scrisă într-un limbaj procedural și nu poate folosi sau modifica variabile sau folosi structuri de flux de control, bazându-se în schimb pe un limbaj declarativ de interogare a bazei de date. Datorită simplității și capacității de emulare din partea clientului (dacă SGBD-ul țintă nu le acceptă), interogările pregătite sunt mai portabile între diferite SGBD decât procedurile stocate.
Aproape toate SGBD-urile comune , inclusiv SQLite , [7] MySQL , [8] Oracle , [9] DB2 , [10] Microsoft SQL Server [11] și PostgreSQL [12] acceptă interogări pregătite. Interogările pregătite sunt de obicei invocate folosind un protocol binar special care pare să mărească rata de transfer de date și ar trebui să protejeze în continuare împotriva injectării SQL, dar unele DBMS, inclusiv de exemplu MySQL, permit, în scopuri de depanare, să apeleze interogări pregătite folosind sintaxa Interogări SQL [13] .
Multe limbaje de programare acceptă interogări pregătite în bibliotecile lor standard și le emulează pentru cazurile în care DBMS-ul țintă nu acceptă această capacitate. Printre aceste limbaje se numără Java (folosind JDBC [14] ), Perl (folosind DBI (perl) [15] ), PHP (folosind PDO [1] ) și Python (folosind DB-API [16] ) . Emularea la nivelul clientului poate fi mai eficientă în ceea ce privește performanța pentru cereri individuale și mai puțin eficientă pentru cereri multiple. De asemenea, ajută împotriva injecțiilor SQL, la fel ca și implementarea directă a interogărilor pregătite pe partea DBMS [17] .
Acest exemplu folosește Java și JDBC :
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource ; import java.sql.Connection ; import java.sql.DriverManager ; import java.sql.PreparedStatement ; import java.sql.ResultSet ; import java.sql.SQLException ; import java.sql.Statement ; clasă publică principală { public static void main ( String [] args ) aruncă SQLException { MysqlDataSource ds = new MysqlDataSource (); ds . setDatabaseName ( "mysql" ); ds . setUser ( "rădăcină" ); try ( Connection conn = ds . getConnection ()) { try ( Statement stmt = conn . createStatement ()) { stmt . executeUpdate ( "CREATE TABLE IF NU EXISTIST products (nume VARCHAR(40), pret INT)" ); } încercați ( PreparedStatement stmt = conn . prepareStatement ( „INSERT INTO products VALUES (?, ?)” )) { stmt . setString ( 1 , „bicicletă” ); stmt . setInt ( 2 , 10900 ); stmt . executeUpdate (); stmt . setString ( 1 , „pantofi” ); stmt . setInt ( 2 , 7400 ); stmt . executeUpdate (); stmt . setString ( 1 , „telefon” ); stmt . setInt ( 2 , 29500 ); stmt . executeUpdate (); } încercați ( PreparedStatement stmt = conn . prepareStatement ( " SELECT * FROM products WHERE name = ?" )) { stmt . setString ( 1 , „pantofi” ); ResultSet rs = stmt . executeQuery (); rs . următorul (); Sistem . afară . println ( rs . getInt ( 2 )); } } } }Java PreparedStatementfurnizează „seteri” ( setInt(int), setString(String), setDouble(double),etc.) pentru toate tipurile majore de date încorporate.
Acest exemplu folosește PHP și PDO :
<?php try { // Conectați-vă la o bază de date numită "mysql", cu parola "root" $connection = new PDO ( 'mysql:dbname=mysql' , 'root' ); // Executați o solicitare pe conexiune, care va crea // un tabel „produse” cu două coloane, „nume” și „preț” $connection -> exec ( 'CREATE TABLE IF NOT EXISTS products (nume VARCHAR(40),) preţ INT)' ); // Pregătește o interogare pentru a insera mai multe produse în tabel $statement = $connection -> prepare ( 'INSERT INTO products VALUES (?, ?)' ); $produse = [ [ 'bicicletă' , 10900 ], [ 'pantofi' , 7400 ], [ 'telefon' , 29500 ], ]; // Iterează prin produsele din matricea „produse” și // execută instrucțiunea pregătită pentru fiecare produs foreach ( $produse ca $produs ) { $statement -> execute ( $produs ); } // Pregătește o nouă instrucțiune cu un parametru numit $statement = $connection -> prepare ( 'SELECT * FROM products WHERE nume = :nume' ); $statement -> execute ([ ':name' => 'pantofi' , ]); // Folosește destructurarea matricei pentru a atribui numele produsului și prețul acestuia // variabilelor corespunzătoare [ $produs , $preț ] = $statement -> fetch (); // Afișează rezultatul pentru utilizatorul ecou "Prețul produsului { $produs } este \$ { $preț } ." ; // Închide cursorul astfel încât `fetch` să poată fi folosit din nou $statement -> closeCursor (); } catch ( \Exception $e ) { echo 'A apărut o eroare: ' . $e -> getMessage (); }Acest exemplu folosește Perl și DBI :
#!/usr/bin/perl -w use strict ; utilizați DBI ; my ( $db_name , $db_user , $db_parola ) = ( 'my_database' , 'moi' , 'Passw0rD' ); my $dbh = DBI -> connect ( "DBI:mysql:database=$db_name" , $db_user , $db_password , { RaiseError => 1 , AutoCommit => 1 }) sau die "EROARE (principal:DBI->connect) în timpul conectării la baza de date $db_name: " . $ DBI:: errstr . „\n” ; $dbh -> do ( 'CREAȚI TABEL DACĂ NU EXISTĂ produse (nume VARCHAR(40), preț INT)' ); my $sth = $dbh -> prepare ( 'INSERT INTO products VALUES (?, ?)' ); $sth -> execute ( @$_ ) foreach [ 'bicicletă' , 10900 ], [ 'pantofi' , 7400 ], [ 'telefon' , 29500 ]; $sth = $dbh -> prepare ( "SELECT * FROM products WHERE name = ?" ); $sth -> execute ( 'pantofi' ); print "$$_[1]\n" pentru fiecare $sth -> fetchrow_arrayref ; $sth -> termina ; $dbh -> deconectare ;Acest exemplu folosește C# și ADO.NET :
folosind ( SqlCommand command = connection . CreateCommand ()) { command . CommandText = "SELECT * FROM users WHERE USERNAME = @username AND ROOM = @room" ; comanda . Parametrii . AddWithValue ( "@username" , nume de utilizator ); comanda . Parametrii . AddWithValue ( "@camera" , camera ); folosind ( SqlDataReader dataReader = comandă . ExecuteReader ()) { // ... } }Acest exemplu utilizează Python și DB-API:
import mysql.connector cu mysql . conector . connect ( baza de date = "mysql" , utilizator = "rădăcină" ) ca conn : cu conn . cursor ( preparat = True ) ca cursor : cursor . execute ( „CREATE TABLE IF NOT EXISTS products (nume VARCHAR(40), preț INT)” ) params = [( „bicicletă” , 10900 ), ( „pantofi” , 7400 ), ( „telefon” , 29500 )] cursor . executemany ( „INSERT INTO products VALUES ( %s , %s )” , params ) params = ( „pantofi” ,) cursor . execute ( "SELECT * FROM produse WHERE nume = %s " , parametri ) print ( cursor . fetchall ()[ 0 ][ 1 ])