You are on page 1of 8

Implementacin de argumentos variables mediante XML en SQL Server 2000

Cmo pasar un nmero variable de argumentos a un procedimiento almacenado de SQL Server usando XML, evitando el uso de SQL dinmico (sp_executesql)
SQL Server 2000 incluye un nuevo conjunto de funciones para el manejo de datos en formato XML. Por ejemplo, es posible devolver datos al cliente en formato XML mediante la habitual sentencia SELECT junto a la clusula FOR XML, o transformar los datos contenidos en un documento XML en un conjunto de filas (registros) con los que trabajar de manera clsica (con sentencias Transact-SQL como SELECT, INSERT, UPDATE o DELETE) mediante OpenXML.

Una de las posibilidades que nos ofrecen stas nuevas funciones es la implementacin de argumentos variables en los procedimientos almacenados. OpenXML permite trabajar con los datos contenidos en un documento XML (en realidad, una cadena de texto plano) como si de un conjunto de registros se tratase, de manera que podemos agregar un argumento a un procedimiento almacenado que reciba una cadena XML donde se incluyan todos los parmetros que este procedimiento necesite. De sta manera, evitamos el uso de SQL dinmico (sp_executesql) para ejecutar instrucciones Transact-SQL generadas en tiempo de ejecucin y los inconvenientes que su uso acarrea (como la peligrosa inyeccin de cdigo SQL) adems de obtener un mejor rendimiento (las instrucciones ejecutadas con EXEC o sp_executesql no son compiladas hasta el momento de la ejecucin y el plan de ejecucin generado por el optimizador de consultas raramente es reutilizado).

Cmo funciona OpenXML?

OpenXML transforma una cadena de texto con estructura XML en un conjunto de registros estndar (como, por ejemplo, el conjunto de registros devuelto por una sentencia SELECT). Para obtener correctamente stos datos, debemos especificar algunos parmetros de OpenXML; los ms comunes son:

OPENXML(num_doc int, patron_filas nvarchar) [WITH (esquema_filas)]

num_doc: Controlador interno del documento (un nmero entero obtenido mediante sp_xml_preparedocument) patron_filas: Patrn XPath que identifica cules de los nodos del documento XML se procesarn esquema_filas: Definicin del tipo de datos que contienen los nodos del documento XML, en formato [nombre_columna, tipo_dato_columna]

Veamos un pequeo ejemplo donde se obtiene un conjunto de registros de dos columnas a partir de un documento XML con la siguiente estructura:

<paises> <pais IdPais="1" Nombre="Francia"/> <pais IdPais="2" Nombre="Italia"/> </paises>

Fig.1 Ejemplo de obtencin de registros mediante OpenXML

Con sta caracterstica de SQL Server podemos resolver un problema muy comn en gran nmero de aplicaciones de gestin: permitir al usuario seleccionar los registros que desea que aparezcan en un determinado informe. Si nuestra tabla tiene un campo que identifique inequvocamente un registro, slo tenemos que armar la cadena XML que incluya todos stos identificadores y pasrsela al procedimiento almacenado, para su uso en las correspondientes sentencias SELECT internas.

Por qu no usar SQL dinmico?


La solucin habitual a ste problema es el uso de SQL dinmico: generar una cadena de texto con las sentencias necesarias para obtener los datos que necesitamos y ejecutarlas mediante EXEC() o sp_executesql; por ejemplo:

DECLARE @ids varchar(20) SET @ids = '5, 6, 7, 8' EXEC('SELECT * FROM Empleados WHERE IdEmpleado IN (' + @ids + ')')

Sin duda, sta es una caracterstica muy potente de SQL Server, pero conviene usarla con cuidado y tener en cuenta sus principales inconvenientes, entre los que destacara los siguientes:

Las sentencias SQL se ejecutan en un alcance distinto del procedimiento que las llam, por lo que no pueden tener acceso a sus parmetros y variables locales. Las tablas temporales creadas en las sentencias SQL se eliminan al terminar stas (por lo que no es posible trabajar con ellas desde el procedimiento que las ejecuta); las opciones SET establecidas en estas sentencias tampoco afectan al procedimiento externo

Para ms informacin sobre este tema, recomiendo encarecidamente la lectura del siguiente artculo de Erland Sommarskog (Microsoft MVP SQL Server) traducido por Simon Hayes:

Las virtudes y maldades del SQL dinmico http://www.hayes.ch/sql/sql_dinamico.html

Caso prctico:

Veamos como el soporte para XML de SQL Server puede ayudarnos en un caso real. Supongamos que tenemos una tabla Empleados con varios cientos de registros, y una aplicacin de gestin donde el usuario puede hacer altas, bajas y modificaciones de stos datos, as como generar informes basados en ellos. Tenemos un formulario donde el usuario puede seleccionar los empleados que desea que aparezcan en un determinado informe:

Fig.2 Formulario de ejemplo para la seleccin de los empleados a incluir en el informe

Vamos a desarrollar un procedimiento almacenado que reciba un argumento XML (en realidad, una cadena de texto varchar) que contendr la informacin con la cual el procedimiento podr identificar y devolver los registros que el usuario desea imprimir.

Manos a la obra!!:
Vamos a crear una base de datos en nuestro servidor y una tabla Empleados que utilizaremos para nuestro proyecto de prueba:

/* Crear la base de datos PruebasXML */ CREATE DATABASE PruebasXML GO USE PruebasXML GO /* Crear la tabla Empleados */ CREATE TABLE Empleados ( IdEmpleado int IDENTITY(1, 1) PRIMARY KEY,

Nombre varchar(20), Apellido1 varchar(20), Apellido2 varchar(20), FechaNacimiento datetime ) GO /* Insertar algunos registros */ INSERT INTO Empleados VALUES ('Jorge', 'Martnez', 'Garca', '19760703') INSERT INTO Empleados VALUES ('Pedro', 'Lpez', 'Valle', '19680319') INSERT INTO Empleados VALUES ('Luis', 'Iglesias', 'Gonzlez', '19961107') INSERT INTO Empleados VALUES ('Alfredo', 'Muiz', 'Surez', '19790901') INSERT INTO Empleados VALUES ('Sergio', 'Rodrguez', 'Alvarez', '19810430') INSERT INTO Empleados VALUES ('Rafael', 'Fuentes', 'Aranda', '19661022') INSERT INTO Empleados VALUES ('Miguel', 'Arias', 'Gmez', '19700101') GO

Una vez creadas la base de datos y la tabla e insertados algunos registros, la vista desde el Analizador de consultas de SQL Server sera la siguiente:

Fig.3 Vista previa de la tabla Empleados en el Analizador de consultas de SQL Server

Crearemos ahora el procedimiento almacenado que devolver un determinado conjunto de empleados, segn la cadena XML recibida:

CREATE PROCEDURE spInformeEmpleados @TextoXML varchar(2000) AS SET NOCOUNT ON DECLARE @DocXML int /* Cargar el documento XML del texto del argumento @TextoXML */ EXEC sp_xml_preparedocument @DocXML output, @TextoXML /* Seleccionar los registros de la tabla Empleados cuyo campo IdEmpleado est incluido en el nodo <empleados/empleado/IdEmpleado> del documento XML */ SELECT * FROM Empleados WHERE IdEmpleado IN ( SELECT IdEmpleado FROM OpenXML(@DocXML, 'empleados/empleado') WITH (IdEmpleado int) ) ORDER BY Apellido1, Apellido2 /* Destruir el documento XML y liberar memoria */ EXEC sp_xml_removedocument @DocXML SET NOCOUNT OFF

ste procedimiento almacenado recibe, en su argumento @TextoXML, una cadena XML que define (a travs de su identificador IdEmpleado) los registros que deseamos que sean devueltos por el procedimiento. Esta cadena XML tendr un formato similar al siguiente:

<empleados> <empleado <empleado <empleado <empleado </empleados>

IdEmpleado="1"/> IdEmpleado="3"/> IdEmpleado="4"/> IdEmpleado="6"/>

El siguiente paso ser crear una pequea aplicacin en Visual Basic 6.0 y ADO que haga uso de ste procedimiento almacenado como origen de datos para un informe. En el evento Load del formulario (Fig.1) cargamos los empleados, leyndolos de la tabla Empleados y mostrndolos en un control ListView:

Private Sub Form_Load() Dim Cn As ADODB.Connection, Rs As ADODB.Recordset Set Cn = New ADODB.Connection Set Rs = New ADODB.Recordset Cn.Open "Provider=SQLOLEDB.1;Initial Catalog=PruebasXML;Data Source=SERVIDORSQL" Rs.Open "SELECT * FROM Empleados ORDER BY Apellido1, Apellido2", Cn, adOpenForwardOnly While Not Rs.EOF '// Cargar el control ListView con los empleados de la base de datos

1)

With lvwEmpleados.ListItems.Add(, , Rs.Collect("Nombre"), , .Tag = Rs.Collect("IdEmpleado") .SubItems(1) = Rs.Collect("Apellido1") .SubItems(2) = Rs.Collect("Apellido2") .SubItems(3) = Rs.Collect("FechaNacimiento") End With Rs.MoveNext

Wend Rs.Close: Set Rs = Nothing Cn.Close: Set Cn = Nothing End Sub

En el evento Click del botn Aceptar (btnAceptar) llamamos al procedimiento almacenado (mediante un objeto ADODB.Command) enviando como nico argumento la cadena XML que contiene los identificadores de los empleados seleccionados:

Private Sub btnAceptar_Click() If lvwEmpleados.SelectedItem Is Nothing Then MsgBox "Debe seleccionar al menos un empleado para el informe", vbCritical Else MostrarInforme GenerarXML End If End Sub Private Sub MostrarInforme(ByVal strXML As String) Dim Cn As ADODB.Connection, Cm As ADODB.Command Set Cn = New ADODB.Connection Set Cm = New ADODB.Command With Cn .Mode = adModeRead .CursorLocation = adUseClient .Open "Provider=SQLOLEDB.1;Initial Catalog=PruebasXML;Data Source=SERVIDORSQL" End With '// Llamar al procedimiento almacenado spInformeEmpleados, con la cadena XML como argumento With Cm .ActiveConnection = Cn .CommandText = "spInformeEmpleados" .CommandType = adCmdStoredProc .Parameters.Append .CreateParameter("@TextoXML", adVarChar, adParamInput, 2000, strXML) MostrarVistaPreviaInforme .Execute End With Cn.Close: Set Cn = Nothing End Sub Private Function GenerarXML() As String

Dim Li As '// la '// For

MSComctlLib.ListItem, strXML As String Generar la cadena XML segn el campo IdEmpleado (almacenado en

propiedad Tag de cada elemento del ListView) Each Li In lvwEmpleados.ListItems If Li.Checked Then strXML = strXML & "<empleado IdEmpleado=""" & Li.Tag & """/>" Next Li GenerarXML = "<empleados>" & strXML & "</empleados >" End Function Private Sub MostrarVistaPreviaInforme(ByRef Rs As ADODB.Recordset) Set DataReport1.DataSource = Rs DataReport1.Show vbModal, frmEmpleados Rs.Close: Set Rs = Nothing End Sub El informe resultante, utilizando un diseador DataReport de Visual Basic 6.0, sera el siguiente:

You might also like