You are on page 1of 4

SQL Dinámico: Exec y sp_executeSql

La ejecución dinámica de instrucciones SQL es algo a lo que tarde o temprano (más bien
temprano) tenemos que enfrentarnos. Y ya que tenemos que hacerlo más vale saber como
funciona y tener un poco de cuidado…

Por qué dinámico


Hablamos de ejecución dinámica porque la sentencia SQL que va a ejecutar nuestro servidor no
esta escrita en ningún sitio, sino que se crea cada vez que tenemos que ejecutarla. Frente a esta
forma de trabajar tenemos el SQL “normal”, donde la sentencia SQL esta previamente escrita y
lo único que hay que hacer es decidir cuando la ejecutamos.

Tipos de SQL dinámico


Siempre es difícil hacer una clasificación, pero en este caso vamos a intentar diferenciar el SQL
dinámico que se crea en el servidor y el que creamos en nuestras aplicaciones.
Por ejemplo si trabajamos en Visual Basic NET y en nuestro código tenemos algo como esto:

Dim sSQL as string


Dim entrada as string
Dim miCon As New SqlConnection(“cadena conexión”)
‘de alguna manera el usuario escribe una condición
‘y metemos ese valor en la variable entrada
sSQL=”SELECT * FROM Clientes WHERE Nombre =“
sSQL=sSQL + “’” + entrada + “`”
Dim cmdProductos As New SqlCommand(sSQL, miCon)
cmdProductos.CommandType = CommandType.Text
miCon.Open()
. . . =cmdProductos.ExecuteReader()
‘ y todo lo que haga falta para usar los datos devueltos . . .

Esto sería SQL dinámico generado en el cliente.


Pero no es esto lo que nos interesa. Nosotros vamos a centrarnos en el SQL generado en el
servidor, generalmente dentro de un procedimiento almacenado.
Primero veamos como podemos pasar el código anterior a un procedimiento almacenado que
recibe un parámetro:

CREATE PROCEDURE VerProductos @Nombre varchar(50) AS


SELECT * FROM Productos WHERE Nombre = @Nombre
GO

¿Más o menos lo mismo no? Pues sí, pero no.


La diferencia está en que cuando creamos la sentencia desde el cliente podemos hacer
prácticamente de todo para editar y construir esa sentencia. Y cuando lo hacemos un
procedimiento almacenado tenemos limitaciones a la hora de utilizar variables para construir
nuestra sentencia.
Supongamos que necesitamos una sentencia SQL donde la parte dinámica es el nombre de la
tabla. Parece lógico escribir algo así:
CREATE PROCEDURE ElegirTabla @NombreTabla VarChar(128) AS
SELECT * FROM @NombreTabla
GO

Pero si lo ejecutamos lo único que obtenemos es un error.

Incorrect syntax near '


@NombreTalba'

¡No podemos utilizar variables en cualquier parte de una sentencia! Por ejemplo no podemos
utilizarlas en la cláusula FROM.

Ejecutar SQL dinámico con EXEC


Ahora es cuando ya tenemos que utilizar SQL dinámico de servidor propiamente dicho. Para
solucionar el error de antes generaremos dentro de nuestro procedimiento almacenado una
sentencia en una variable de texto como hacíamos desde Visual Basic NET y luego le diremos al
SQL Server que ejecute esa sentencia mediante el comando EXEC.

CREATE PROCEDURE ElegirTabla @NombreTabla VarChar(128) AS


DECLARE @sSQL VarChar(1000)
--El truco esta en tener la sentencia dentro de una variable
SELECT sSQL = ' SELECT * FROM '
SELECT @sSQL = @SQL + @NombreTabla
--Y ejecutar ese texto contenido en nuestra variable
EXEC ( @sSQL)
GO

Como vemos es sencillo, pero utilizar el SQL así dentro de un procedimiento almacenado nos
ocasiona dos problemas.
En primer lugar utilizar así los parámetros da lugar a problemas de seguridad porque estamos
permitiendo que el usuario utilice trucos de inyección de código y que nuestro procedimiento
almacenado haga cosas para las que no fue creado.
Un ejemplo de lo que alguien podría llegar a hacer es:

EXEC ElegirTabla ‘ Clientes GO DROP TABLE Pedidos'


''

Que un usuario pueda ejecutar sentencias como esta no es algo muy deseable verdad.

El segundo problema que nos plantea el uso de EXEC es de rendimiento. Como ya sabemos
una de las ventajas de los procedimientos almacenados es que cuando se ejecuta la primera vez
se compila, y no es necesario recompilarlo en ejecuciones sucesivas. Pero esto deja de ser
cierto cuando usamos EXEC para ejecutar SQL dinámico porque cada ejecución requiere una
compilación.
Esto nos lleva al segundo modo de ejecutar SQL dinámico dentro de un procedimiento
almacenado.

SP_EXECUTESQL

El procedimiento 'sp_executesql’ puede utilizarse como alternativa a los procedimientos


almacenados para ejecutar varias veces una instrucción de Transact-SQL si la única
modificación es que cambian los valores de los parámetros. Como dicen los BOL, al permanecer
constante la propia instrucción SQL y variar sólo los valores de los parámetros, es probable que
el optimizador de consultas del SQL Server vuelva a utilizar el plan de ejecución que genera para
la primera ejecución.
El funcionamiento de sp_executesql es el siguiente:
sp_executesql [@stmt =] stmt
[
{, [@params =] N'@parameter_name data_type [,...n]'}
{, [@param1 =] '
value1'[,...n] }
]

Usar este procedimiento almacenado soluciona en gran parte los dos problemas que veíamos
con la ejecución dinámica a través de EXEC.
Por un lado lo único que se transmite a sp_executesql son los parámetros y eso evita la
inyección de código, y por otro lado se puede reutilizar el plan de ejecución.
Veamos un ejemplo de cómo utilizar este procedimiento.

USE NorthWind
DECLARE @sSQL nvarchar(1000)
--la variabla que contiene la cadena a ejecutar debe ser unicode, por eso lo de nvarchar
SET @sSQL = ' SELECT * FROM Products WHERE ProductName = @NombreProducto'
EXEC sp_executesql @sSQL, N' @NombreProducto nvarchar(50)' , @NombreProducto = ' chai'

Como vemos el primer parámetro es la sentencia que queremos ejecutar, después viene
declarada la variable que vamos a usar y por último le damos valor a esa variable.
Seguro que se os ocurren un par de utilidades para sp_executesql.

Más sobre SP_EXECUTESQL

Sp_executesql tiene unas cuantas peculiaridades que conviene tener en cuenta.


Para empezar la ejecución de una sentencia a traves de sp_executesql se hace en un ámbito
propio. Esto quiere decir que las variables declaradas fuera no se pueden utilizar, que las
declaradas dentro no se pueden leer desde fuera, que podemos cambiar la base de datos
(utilizar USE) en la que trabajar dentro del sp_executesql, crear tablas temporales en el nuevo
ámbito, utilizar sentencias SET sin hacer cambios en la configuración del procedimiento
llamante, y por último aunque no menos importante que si se produce un error y se aborta la
instrucción también se aborta el procedimiento en el que se está ejecutando.

En cuanto a los permisos recordar algo muy importante. La sentencia SQL se ejecuta dentro del
contexto de seguridad del usuario, no del procedimiento que llama a esa sentencia.

Un ejemplo de cómo usar y como no usar SQL dinámico


Para concluir vamos a ver un ejemplo donde podemos usar SQL dinámico.
A veces tenemos que crear una sentencia que ordene una tabla según una columna que
dependerá de algún parámetro que nos pasa el usuario. Eso lo podemos resolver utilizando un
procedimiento almacenado y una sentencia como:

CREATE PROCEDURE OrdenarPor @Columna varchar(50) AS


DECLARE @sSQL varchar(50)
SET @sSQL=’SELECT * FROM MiTabla ORDER BY ’ + @Columna
EXEC @sSQL

Esto está en la línea de todo lo que hemos estado diciendo.


Es una solución sencilla pero que como no nos hemos cansado de repetir plantea varios
problemas de seguridad y rendimiento. Así que ¿por que no pensamos un poco e intentamos
crear un procedimiento almacenado que haga lo mismo?
Creedme que pocas veces encontraremos una tarea que no se pueda resolver sin recurrir al
SQL dinámico.
Fijémonos en lo siguiente:
CREATE PROCEDURE OrdenarPor @Columna varchar(50) AS
SELECT * FROM miTabla ORDER BY
CASE @Columna
WHEN '
columna1'THEN columna1
WHEN '
columna2'THEN columna2
WHEN '
columna3'THEN columna3
END

Cierto que es un poco más complicado (se utiliza la función CASE), pero no hay inyección de
código posible y una vez calculado el plan de ejecución se utilizará sin recompilar una y otra vez

Como conclusión nos queda que el SQL dinámico es una herramienta potente pero peligrosa si
no se trata con cuidado, y que la mayoría de las veces no necesitamos recurrir a ella porque
siempre podemos encontrar otra solución.

César Manivesa
sql@manivesa.com
http://sql.manivesa.com

You might also like