Articulo de referencia

Declaración preparada

En los sistemas de gestión de bases de datos (DBMS), una sentencia preparada , sentencia parametrizada (que no debe confundirse con una consulta parametrizada ) es una caracterí...

En los sistemas de gestión de bases de datos (DBMS), una sentencia preparada , sentencia parametrizada (que no debe confundirse con una consulta parametrizada ) es una característica en la que la base de datos precompila el código SQL y almacena los resultados, separándolos de los datos. Los beneficios de las sentencias preparadas son: [ 1 ]

  • eficiencia, porque se pueden usar repetidamente sin necesidad de recompilar.
  • seguridad, al reducir o eliminar los ataques de inyección SQL

Una sentencia preparada adopta la forma de una plantilla precompilada en la que se sustituyen valores constantes durante cada ejecución, y normalmente utiliza sentencias SQL DML como INSERT , SELECT o UPDATE .

Un flujo de trabajo común para los estados financieros preparados es:

  1. Preparación : La aplicación crea la plantilla de sentencia y la envía al sistema de gestión de bases de datos (DBMS). Algunos valores se dejan sin especificar, denominados parámetros , marcadores de posición o variables de enlace (etiquetados como "?" a continuación):
    INSERTINTOproducts(name,price)VALUES(?,?);
  2. Compilación : El sistema de gestión de bases de datos (DBMS) compila (analiza, optimiza y traduce) la plantilla de la instrucción y almacena el resultado sin ejecutarla.
  3. Ejecutar : La aplicación proporciona (o vincula ) valores para los parámetros de la plantilla de instrucción, y el sistema de gestión de bases de datos (DBMS) ejecuta la instrucción (posiblemente devolviendo un resultado). La aplicación puede solicitar al DBMS que ejecute la instrucción varias veces con diferentes valores. En el ejemplo anterior, la aplicación podría proporcionar los valores "bike" para el primer parámetro y "10900" para el segundo, y posteriormente los valores "shoes" y "7400".

La alternativa a una sentencia preparada es llamar a SQL directamente desde el código fuente de la aplicación, combinando código y datos. El equivalente directo al ejemplo anterior es:

INSERTAR EN productos ( nombre , precio ) VALORES ( 'bicicleta' , '10900' );

No todas las optimizaciones se pueden realizar en el momento en que se compila la plantilla de sentencia, por dos razones: el mejor plan puede depender de los valores específicos de los parámetros, y el mejor plan puede cambiar a medida que las tablas y los índices cambian con el tiempo. [ 2 ]

Por otro lado, si una consulta se ejecuta solo una vez, las sentencias preparadas del lado del servidor pueden ser más lentas debido al viaje de ida y vuelta adicional al servidor. [ 3 ] Las limitaciones de implementación también pueden generar penalizaciones de rendimiento; por ejemplo, algunas versiones de MySQL no almacenaban en caché los resultados de las consultas preparadas. [ 4 ] Un procedimiento almacenado , que también se precompila y almacena en el servidor para su posterior ejecución, tiene ventajas similares. A diferencia de un procedimiento almacenado, una sentencia preparada normalmente no se escribe en un lenguaje procedimental y no puede usar ni modificar variables ni usar estructuras de flujo de control , sino que se basa en el lenguaje de consulta de base de datos declarativo . Debido a su simplicidad y emulación del lado del cliente, las sentencias preparadas son más portátiles entre proveedores.

Soporte de software

Los principales sistemas de gestión de bases de datos (DBMS) , incluidos SQLite , [ 5 ] MySQL , [ 6 ] Oracle , [ 7 ] IBM Db2 , [ 8 ] Microsoft SQL Server [ 9 ] y PostgreSQL, [ 10 ] admiten sentencias preparadas. Estas sentencias se ejecutan normalmente mediante un protocolo binario no SQL para mayor eficiencia y protección contra la inyección SQL, pero en algunos DBMS, como MySQL, también están disponibles mediante sintaxis SQL para fines de depuración. [ 11 ]

Varios lenguajes de programación admiten sentencias preparadas en sus bibliotecas estándar y las emulan en el lado del cliente incluso si el DBMS subyacente no las admite, incluyendo JDBC de Java , [ 12 ] DBI de Perl , [ 13 ] PDO de PHP [ 1 ] y DB-API de Python . [ 14 ] La emulación del lado del cliente puede ser más rápida para consultas que se ejecutan solo una vez, al reducir el número de viajes de ida y vuelta al servidor, pero suele ser más lenta para consultas que se ejecutan muchas veces. Resiste los ataques de inyección SQL con la misma eficacia.

Muchos tipos de ataques de inyección SQL pueden eliminarse deshabilitando los literales , lo que efectivamente requiere el uso de sentencias preparadas; a partir de 2007Solo H2 admite esta función. [ 15 ]

Ejemplos

C++

En C++ MySQL Connector/C++ X DevAPI [ 16 ] [ 17 ] , la preparación se realiza implícitamente utilizando la API de la biblioteca.

import < mysqlx / xdevapi . h > import std ;using mysqlx :: Schema ; using mysqlx :: Session ; using mysqlx :: Table ;int main () { try { Session session ( "localhost" , 33060 , "user" , "password" ); Schema db = session . getSchema ( "testdb" ); Table users = db . getTable ( "users" );usuarios.insertar ( "nombre" , "edad" ) . valores ( "Alice" , 30 ) .ejecutar ( ); } catch ( const mysqlx :: Error & e ) { std :: println ( stderr , " Error de MySQL: {}" , e.what ( ) ) ; } }

C# ADO.NET

Este ejemplo utiliza C# y ADO.NET :

espacio de nombres Wikipedia.Ejemplos ;using System ; using System.Data ;usando Microsoft.Data.SqlClient ;public class Example { static void Main ( string [] args ) { using ( SqlCommand command = connection . CreateCommand ()) { command . CommandText = "SELECT * FROM users WHERE USERNAME = @username AND ROOM = @room" ; command . Parameters . AddWithValue ( "@username" , username ); command . Parameters . AddWithValue ( "@room" , room );using ( SqlDataReader dataReader = command . ExecuteReader ()) { // ... } } } }

ADO.NET SqlCommandacepta cualquier tipo para el valueparámetro de AddWithValue, y la conversión de tipo se realiza automáticamente. Tenga en cuenta el uso de "parámetros con nombre" (es decir, "@username") en lugar de "?"; esto le permite usar un parámetro varias veces y en cualquier orden arbitrario dentro del texto del comando de consulta.

Sin embargo, el método AddWithValue no debe utilizarse con tipos de datos de longitud variable, como varchar y nvarchar. Esto se debe a que .NET asume que la longitud del parámetro es la misma que la del valor dado, en lugar de obtener la longitud real de la base de datos mediante reflexión. Como consecuencia, se compila y almacena un plan de consulta diferente para cada longitud distinta. En general, el número máximo de planes "duplicados" es el producto de las longitudes de las columnas de longitud variable especificadas en la base de datos. Por este motivo, es importante utilizar el método Add estándar para columnas de longitud variable.

command.Parameters.Add(ParamName,VarChar,ParamLength).Value=ParamValue, donde ParamLength es la longitud especificada en la base de datos.

Dado que el método Add estándar debe utilizarse para tipos de datos de longitud variable, es una buena práctica usarlo para todos los tipos de parámetros.

Ir

// Define un tipo BookModel que encapsula un pool de conexiones sql.DB. type BookModel struct { DB * sql . DB }// Esto insertará un nuevo libro en la base de datos. func ( m * BookModel ) Insert ( title , author string ) ( int , error ) { stmt := "INSERT INTO book (title, author, created) VALUES(?, ?, UTC_TIMESTAMP())" // La función "Exec" preparará automáticamente la sentencia por usted, // lo que requiere un viaje de ida y vuelta adicional a la base de datos. // // Es posible evitar las sentencias preparadas, si está seguro de que no son necesarias. // Consulte ExecerContext para obtener más detalles. https://pkg.go.dev/database/sql/driver#ExecerContext // // Otras funciones como "Query" funcionan de la misma manera, // y tienen una interfaz equivalente. result , err := m . DB . Exec ( stmt , title , author ) if err != nil { return 0 , err }id , err := result.LastInsertId ( ) // No es compatible con el controlador de Postgres; use RETURNING en su lugar. if err ! = nil { return 0 , err }// El ID devuelto es de tipo int64, por lo que lo convertimos a un tipo int // antes de devolverlo. // // Ten en cuenta que, en máquinas de 32 bits, esto puede truncar el valor. // Normalmente es más seguro devolver int64 directamente desde tu función, // lo cual todavía es compatible con máquinas de 32 bits. return int ( id ), nil }

La sintaxis del parámetro de marcador de posición difiere según la base de datos. MySQL, SQL Server y SQLite usan la  notación ?, pero PostgreSQL usa la notación $N. Por ejemplo, si usara PostgreSQL, escribiría:

_ , err := m . DB . Exec ( "INSERT INTO ... VALUES ($1, $2, $3)" , ... )

Java JDBC

Este ejemplo utiliza Java y JDBC :

paquete org.wikipedia.examples ;import java.sql.Connection ; import java.sql.DriverManager ; import java.sql.PreparedStatement ; import java.sql.ResultSet ; import java.sql.SQLException ; import java.sql.Statement ;import com.mysql.jdbc.jdbc2.optional.MysqlDataSource ;clase pública Main {public static void main ( String [] args ) throws SQLException { MysqlDataSource ds = new MysqlDataSource (); ds . setDatabaseName ( "mysql" ); ds . setUser ( "root" );try ( Connection conn = ds . getConnection ()) { try ( Statement stmt = conn . createStatement ()) { stmt . executeUpdate ( "CREATE TABLE IF NOT EXISTS products (name VARCHAR(40), price INT)" ); }try ( PreparedStatement stmt = conn . prepareStatement ( "INSERT INTO products VALUES (?, ?)" )) { stmt . setString ( 1 , "bike" ); stmt . setInt ( 2 , 10900 ); stmt . executeUpdate (); stmt . setString ( 1 , "shoes" ); stmt . setInt ( 2 , 7400 ); stmt . executeUpdate (); stmt . setString ( 1 , "phone" ); stmt . setInt ( 2 , 29500 ); stmt . executeUpdate (); }try ( PreparedStatement stmt = conn . prepareStatement ( "SELECT * FROM products WHERE name = ?" )) { stmt . setString ( 1 , "shoes" ); ResultSet rs = stmt . executeQuery (); rs . next (); System . out . println ( rs . getInt ( 2 )); } } } }

Java PreparedStatementproporciona "setters" ( setInt(int), setString(String), setDouble(double),etc.) para todos los principales tipos de datos integrados.

PHP PDO

Este ejemplo utiliza PHP y PDO : [ 18 ]

<?php// Conéctate a una base de datos llamada "mysql", con la contraseña "root" $connection = new PDO ( 'mysql:host=127.0.0.1;dbname=test;charset=utf8mb4' , 'root' );// Ejecutar una solicitud en la conexión, que creará // una tabla "productos" con dos columnas, "nombre" y "precio" $connection -> exec ( 'CREATE TABLE IF NOT EXISTS products (name VARCHAR(40), price INT)' );// Prepara una consulta para insertar varios productos en la tabla $statement = $connection -> prepare ( 'INSERT INTO products VALUES (?, ?)' ); $products = [ [ 'bike' , 10900 ], [ 'shoes' , 7400 ], [ 'phone' , 29500 ], ];// Iterar a través de los productos en el array "products" y // ejecutar la sentencia preparada para cada producto foreach ( $products as $product ) { $statement -> execute ( $product ); }// Prepara una nueva sentencia con un parámetro con nombre $statement = $connection -> prepare ( 'SELECT * FROM products WHERE name = :name' ); $statement -> execute ([ ':name' => 'shoes' , ]);// Usa la desestructuración de la matriz para asignar el nombre del producto y su precio // a las variables correspondientes [ $product , $price ] = $statement -> fetch ();// Mostrar el resultado al usuario echo "El precio del producto { $product } es \$ { $price } ." ;

Perl DBI

Este ejemplo utiliza Perl y DBI :

#!/usr/bin/env perl -w use strict ; use DBI ;my ( $db_name , $db_user , $db_password ) = ( 'my_database' , 'moi' , 'Passw0rD' ); my $dbh = DBI -> connect ( "DBI:mysql:database=$db_name" , $db_user , $db_password , { RaiseError => 1 , AutoCommit => 1 }) or die "ERROR (main:DBI->connect) while connecting to database $db_name: " . $ DBI:: errstr . "\n" ;$dbh -> hacer ( 'CREATE TABLE IF NOT EXISTS products (name VARCHAR(40), price INT)' );mi $sth = $dbh -> prepare ( 'INSERT INTO products VALUES (?, ?)' ); $sth -> execute ( @$_ ) foreach [ 'bike' , 10900 ], [ 'shoes' , 7400 ], [ 'phone' , 29500 ];$sth = $dbh -> prepare ( "SELECT * FROM products WHERE name = ?" ); $sth -> execute ( 'shoes' ); print "$$_[1]\n" foreach $sth -> fetchrow_arrayref ; $sth -> finish ;$dbh -> desconectar ;

API de base de datos en Python

Este ejemplo utiliza Python y la API de base de datos:

importar mysql.connectorwith mysql . connector . connect ( database = "mysql" , user = "root" ) as conn : with conn . cursor ( prepared = True ) as cursor : cursor . execute ( "CREATE TABLE IF NOT EXISTS products (name VARCHAR(40), price INT)" ) params : list [ tuple [ str , int ]] = [( "bike" , 10900 ), ( "shoes" , 7400 ), ( "phone" , 29500 )] cursor . executemany ( "INSERT INTO products VALUES ( %s , %s )" , params ) params = ( "shoes" ,) cursor . execute ( "SELECT * FROM products WHERE name = %s " , params ) print ( cursor . fetchall ()[ 0 ][ 1 ])

SQL directo mágico

Este ejemplo utiliza Direct SQL de lenguajes de cuarta generación como eDeveloper, uniPaaS y magic XPA de Magic Software Enterprises.

Nombre de usuario virtual Alpha 20 inicialización: 'hermana' Contraseña virtual Alpha 20 init: 'yellow' Comando SQL: SELECT*FROMusersWHEREUSERNAME=:1ANDPASSWORD=:2 Argumentos de entrada: 1: nombre de usuario 2: contraseña

PureBasic

PureBasic (desde la versión 5.40 LTS) puede gestionar 7 tipos de enlaces con los siguientes comandos:

Establecer base de datos Blob , Establecer base de datos Double , Establecer base de datos Float , Establecer base de datos Long , Establecer base de datos Null , Establecer base de datos Quad , Establecer base de datos String

Existen dos métodos diferentes según el tipo de base de datos.

Para SQLite , ODBC , MariaDB/Mysql use:  ?

SetDatabaseString ( # Base de datos , 0 , "prueba" ) Si DatabaseQuery ( # Base de datos , "SELECT * FROM employee WHERE id=?" ) ; ... EndIf

Para uso en PostgreSQL : $1, $2, $3, ...

SetDatabaseString ( # Base de datos , 0 , "Smith" ) ; -> $1 SetDatabaseString ( # Base de datos , 1 , "Sí" ) ; -> $2 SetDatabaseLong ( # Base de datos , 2 , 50 ) ; -> $3Si DatabaseQuery ( # Base de datos , "SELECT * FROM employee WHERE id=$1 AND active=$2 AND years>$3" ) ; ... FinSi

Véase también

Referencias

  1. 1 2 El Grupo de Documentación de PHP. "Declaraciones preparadas y procedimientos almacenados" . Manual de PHP . Consultado el 25 de septiembre de 2011 .
  2. Petrunia, Sergey (28 de abril de 2007). "Optimizador MySQL y sentencias preparadas" . Blog de Sergey Petrunia . Archivado del original el 5 de febrero de 2018. Consultado el 25 de septiembre de 2011 .
  3. Zaitsev, Peter (2 de agosto de 2006). "MySQL Prepared Statements" . Blog de rendimiento de MySQL . Consultado el 25 de septiembre de 2011 .
  4. "7.6.3.1. Cómo funciona la caché de consultas" . Manual de referencia de MySQL 5.1 . Oracle . Consultado el 26 de septiembre de 2011 .
  5. "Objetos de sentencia preparada" . SQLite . 18 de octubre de 2021.
  6. Oracle. "20.9.4. Sentencias preparadas de la API C" . Manual de referencia de MySQL 5.5 . Consultado el 27 de marzo de 2012 .
  7. "13 Oracle Dynamic SQL" . Guía del programador del precompilador Pro*C/C++, versión 9.2 . Oracle . Consultado el 25 de septiembre de 2011 .
  8. ^ "SQL: Pengertian, Sejarah, Fungsi y Jenis Perintah SQL" .
  9. "SQL Server 2008 R2: Preparación de sentencias SQL" . Biblioteca MSDN . Microsoft . Consultado el 25 de septiembre de 2011 .
  10. "PREPARAR" . Documentación de PostgreSQL 9.5.1 . Grupo de Desarrollo Global de PostgreSQL . Consultado el 27 de febrero de 2016 .
  11. Oracle. "12.6. Sintaxis SQL para sentencias preparadas" . Manual de referencia de MySQL 5.5 . Consultado el 27 de marzo de 2012 .
  12. "Uso de sentencias preparadas" . Los tutoriales de Java . Oracle . Consultado el 25 de septiembre de 2011 .
  13. Bunce, Tim. "Especificación DBI-1.616" . CPAN . Consultado el 26 de septiembre de 2011 .
  14. "Python PEP 289: Especificación de la API de base de datos de Python v2.0" .
  15. "Inyecciones SQL: Cómo no quedarse atascado" . The Codist. 8 de mayo de 2007. Consultado el 1 de febrero de 2010 .
  16. Oracle Corporation (20 de enero de 2026). [dev.mysql.com/doc/dev/connector-cpp/latest/ "Documentación del conector MySQL/C++"]. dev.mysql.com . Oracle Corporation.{{cite web}}: Comprobar |url=valor ( ayuda )
  17. Oracle Corporation. "Guía del usuario de X DevAPI" . dev.mysql.com . Oracle Corporation . Consultado el 1 de marzo de 2026 .
  18. "Entrada manual de PHP para PDO::prepare()" .