<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7580367085552365646</id><updated>2011-08-17T13:28:10.022-07:00</updated><category term='SQL Server'/><title type='text'>.:: Mariano Minoli ::.</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://mminoli.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7580367085552365646/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://mminoli.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Mariano Minoli</name><uri>http://www.blogger.com/profile/08353467425964487192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>2</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7580367085552365646.post-7690414847099744733</id><published>2007-09-29T10:30:00.000-07:00</published><updated>2008-11-15T06:21:24.463-08:00</updated><title type='text'>Construcción de un sistema de auditoría con triggers en Microsoft SQL Server</title><content type='html'>&lt;div&gt;&lt;span style="font-family:verdana;font-size:130%;"&gt;&lt;strong&gt;Construcción de un sistema de auditoría con triggers en Microsoft SQL Server&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style="font-family:verdana;"&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style="font-family:verdana;font-size:78%;"&gt;Por Mariano Minoli.&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style="font-family:verdana;"&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style="font-family:verdana;"&gt;&lt;strong&gt;Introducción&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style="font-family:verdana;"&gt;&lt;span style="font-size:85%;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style="font-family:verdana;"&gt;&lt;span style="font-size:85%;"&gt;La auditoria de sistemas es un punto importante a considerar en sistemas cliente / servidor en los cuales existe una multitud de usuarios trabajando sobre una misma fuente de datos. Cuando dichos sistemas son de pequeño porte y poseen pocos usuarios el problema podría parecer trivial, pero a medida que la envergadura del sistema crece y (sobre todo) la cantidad y variedad de usuarios es mayor, la auditoria se convierte en un punto difícil de manejar. En este artículo se intenta presentar dos soluciones posibles al problema y desarrollar una de ellas en la cual solo se utiliza transact-SQL, por último se darán las bases para automatizar la instalación de la solución presentada y algunas consideraciones y optimizaciones posibles.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;span style="font-family:verdana;"&gt;&lt;span style="font-size:85%;"&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;strong&gt;Sobre auditoría&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style="font-size:100%;"&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;Antes que nada habría que aclarar a que nos referimos cuando hablamos de "auditoria". En este ámbito, con auditoria nos referimos al seguimiento de las operaciones realizadas por cada usuario, básicamente llevar un control de "que se hace", "quien lo hace (el Usuario)", "en donde se hace", y "cuando se hace".&lt;br /&gt;El concepto no es nuevo, en los entornos mainframe suele ser llamado "el login del sistema", aquel lector que esté relacionado con la jerga entenderá que nos referimos a lo mismo, solo diferimos en la forma de implementarlo.&lt;br /&gt;También podría hallarse una cierta analogía con los eventos visualizados en la sección de Seguridad del Visor de Eventos de Windows NT/2000. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="font-size:100%;"&gt;Diferentes enfoques para un mismo problema&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;span style="font-size:100%;"&gt;&lt;/span&gt;&lt;/strong&gt; &lt;/div&gt;&lt;div&gt;Primero buscaremos donde almacenar la información, mas allá de cómo hacerlo. Puede que el lector esté pensando en una tabla en la cual se almacenen datos que contesten a las preguntas hechas en el párrafo anterior, no vamos a contradecirlo: las dos soluciones propuestas incluyen una tabla almacenada en la base de datos. El formato de la tabla puede variar dependiendo de las necesidades específicas del entorno, nosotros proponemos la siguiente, a los efectos del desarrollo:&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;/div&gt;&lt;a href="http://4.bp.blogspot.com/_sdjuoL-kiAQ/Rv6MsPAn4bI/AAAAAAAAAAM/Sgqn_CZo4ps/s1600-h/TablaAuditoria.gif"&gt;&lt;img id="BLOGGER_PHOTO_ID_5115680918310740402" style="FLOAT: left; MARGIN: 0px 10px 10px 0px; CURSOR: hand" alt="" src="http://4.bp.blogspot.com/_sdjuoL-kiAQ/Rv6MsPAn4bI/AAAAAAAAAAM/Sgqn_CZo4ps/s320/TablaAuditoria.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt; &lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-size:85%;"&gt;El script para la creación de la tabla es:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;CREATE TABLE [dbo].[AUDIT] ( &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;[id_evento] [int] IDENTITY (1, 1) NOT NULL , &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;[tipo_evento] [char] (10) NOT NULL , &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;[fecha] [datetime] NOT NULL , &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;[descripcion] [char] (50)  NULL , &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;[usuario] [char] (30)  NULL , &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;[terminal] [char] (30)  NULL , &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;[aplicacion] [char] (30)  NULL) ON [PRIMARY]GOALTER TABLE [dbo].[AUDIT] WITH NOCHECK ADD CONSTRAINT [PK_AUDIT] PRIMARY KEY  NONCLUSTERED (  [id_evento] )  ON [PRIMARY]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;font-size:85%;"&gt;GO&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-size:85%;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-size:85%;"&gt;Ahora que sabemos donde vamos a almacenar la información, necesitamos determinar como vamos a hacerlo. Presentamos dos opciones: &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-size:85%;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;ol&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;Delegar la responsabilidad en la lógica de negocio de la aplicación.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;Crear disparadores en cada tabla que se desee auditar.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;span style="font-size:85%;"&gt;&lt;div&gt;&lt;span style="font-size:100%;"&gt;&lt;strong&gt;1er opción: Delegar la responsabilidad en la lógica de negocio de la aplicación.&lt;br /&gt;&lt;/strong&gt;&lt;/span&gt;&lt;br /&gt;Con esto nos referimos a que dentro del código encargado de hacer modificaciones a los datos, se realicen las inserciones a la tabla AUDIT, de este modo, cualquier operación generará un registro de auditoria. La ubicación del código (responsable de las inserciones) dependerá de la arquitectura que utilice el desarrollador, ya sea en una arquitectura de dos o tres capas, en donde se concentre la lógica de negocio de la aplicación, allí estará el código encargado de realizar las inserciones de auditoria.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;strong&gt;2da opción: Crear disparadores en cada tabla que se desee auditar.&lt;/strong&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;De este modo, cada tabla (que se desee auditar) tendrá un disparador (o trigger) que, cada vez que detecte una modificación en la tabla, inserte un registro en la tabla de auditoria. Si el lector desconoce el concepto de disparador, en las secciones siguientes describiremos en detalle de que se trata, por ahora veámoslo como "algo" que está "latente" en la base de datos que, al detectar una modificación en los datos de una determinada tabla, ejecuta una serie de sentencias Transact - SQL previamente configuradas.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;La opción que se va a desarrollar es la segunda, la decisión no es arbitraria, obedece a una serie de ventajas que se detallan a continuación:&lt;br /&gt;&lt;br /&gt;Primero que nada, el hecho de descargar lógica del lado del cliente suele ser PELIGROSO, en este caso las aplicaciones se hacen mas complejas de programar y mantener. Si se usan disparadores como solución se esta reutilizando código implícitamente, cualquier nueva aplicación que acceda a una tabla existente, será "automáticamente" auditada sin necesidad de escribir una sola línea de código de más.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Además, hay que tener en cuenta que si el sistema es accedido a través de Webforms (ASP.Net) no será bueno cargar al cliente de trabajo que se debería concentrar del lado del servidor.&lt;br /&gt;Por último, como hemos dicho antes, es posible que existan "múltiples usuarios" accediendo de "múltiples maneras" al gestor. Muchas de estas "maneras" pueden no ser aplicaciones propietarias, un usuario por ejemplo podría acceder a través del Query Analyzer y ejecutar una sentencia delete a una tabla cualquiera, si la auditoria se encuentra en el cliente que hemos programado, nunca sabremos quien fue. El lector debe tener en cuenta que NO existe manera de modificar datos en una base de datos SQL Server y evitar que se ejecuten los disparadores que estén asociados a estos datos.&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-size:100%;"&gt;&lt;strong&gt;Conceptos teóricos y construcciones utilizadas&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Para poder comprender correctamente las secciones siguientes, es necesario tener claros algunos conceptos de teoría de bases de datos y la sintaxis de algunas sentencias:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;strong&gt;Desencadenador (trigger)&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;Podría definirse como un tipo especial de procedimiento almacenado que se ejecuta automáticamente cuando un usuario intenta modificar datos sobre una tabla determinada. Los disparadores se almacenan en la base de datos en forma similar a los procedimientos almacenados, sin embargo NUNCA son ejecutados explícitamente por los usuarios, sólo se disparan cuando el gestor detecta una modificación.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Existen tres tipos de disparadores, uno para cada tipo de actualización:&lt;br /&gt;Eliminación&lt;br /&gt;Inserción&lt;br /&gt;Modificación&lt;/div&gt;&lt;div&gt;&lt;br /&gt;La sintaxis que utilizaremos durante este desarrollo para la creación de disparadores es la siguiente:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;CREATE TRIGGER nombre_trigger ON tabla &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;[WITH ENCRYPTION]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;{ {FOR { [DELETE] [,] [INSERT] [,] [UPDATE] } &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;AS sentencia_sql [...n]}&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Cabe aclarar que existen otras opciones adicionales que no son utilizadas durante este desarrollo.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;strong&gt;Sentencia insert utilizada con un select&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;Debido a que no es la sintaxis más popular de un insert, nos pareció interesante explicarle que se va a utilizar la cláusula de la siguiente manera:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Insert into TABLA _ DESTINO&lt;/div&gt;&lt;div&gt;Select * from TABLA _ ORIGEN&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Esta sentencia inserta todos los registros obtenidos como el resultado de la consulta a la TABLA_ORIGEN en la TABLA_DESTINO&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;strong&gt;Execute&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;Puede ser utilizado para ejecutar un procedimiento almacenado o (como en nuestro caso) para ejecutar un conjunto de sentencias Transact-SQL que es pasada como un string, esto nos permite "armar" la sentencia concatenando partes fijas con variables que van tomando distintos valores.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;strong&gt;Las funciones getdate(), host_name(), SYSTEM_USER, APP_NAME&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;Usamos estas funciones para "capturar" valores que nos interesa almacenar en la tabla AUDIT. Sus significados son:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Getdate(): Fecha-hora actual del sistema.&lt;br /&gt;SYSTEM_USER: Nombre del usuario que envía una sentencia al servidor.&lt;br /&gt;host_name(): Nombre de la maquina cliente desde donde se envía la sentencia.&lt;br /&gt;APP_NAME: Nombre de la aplicación cliente a través de la cual se envía la sentencia.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;strong&gt;Desarrollo&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Con lo que se ha explicado hasta el momento, poco nos resta para implementar el sistema de auditoria, simplemente hay que crear un disparador por cada tabla que se quiera auditar, el disparador tiene que ejecutar un insert a la tabla AUDIT, veámoslo con un ejemplo. Supongamos una tabla CLIENTES, que presenta la siguiente estructura (muy simplificada, solo para fines demostrativos):&lt;br /&gt;&lt;br /&gt;La sentencia de creación de esta tabla es:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;CREATE TABLE [dbo].[CLIENTES] ( &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;[id_cliente] [int] NOT NULL , &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;[Nombre] [char] (50) NOT NULL) &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;ON [PRIMARY]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;GO&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Para esta tabla las sentencias de creación de los disparadores son las siguientes:&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;CREATE TRIGGER AUDITdel ON CLIENTES &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;FOR DELETE&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;AS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;Insert into AUDIT select "Delete", getdate(), "Eliminacion de un registro", SYSTEM_USER, host_name(),APP_NAME()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;CREATE TRIGGER AUDITins ON CLIENTES &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;FOR INSERT&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;AS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;insert into AUDITselect "Insert", getdate(), "Insercion de un registro", SYSTEM_USER, host_name(),APP_NAME()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;CREATE TRIGGER AUDITdel ON CLIENTES &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;FOR UPDATE&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;AS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;insert into AUDITselect "Update", getdate(), "Modificacion de un registro", SYSTEM_USER, host_name(),APP_NAME()&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Cabe aclarar que lo mismo se podría hacer de una manera más "amigable" desde el Administrador Corporativo (Enterprise Manager).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Habiendo creado los tres disparadores sobre la tabla clientes, intente hacer cualquier operación que modifique los datos de la misma, por ejemplo podría ejecutar las tres sentencias siguientes:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Insert  into CLIENTES values (2356, "Juan Perez")&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;Go&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;Update CLIENTES set Nombre = "Ricardo Perez" &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;where  id_cliente = 2356&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;Go&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;Delete CLIENTES where  id_cliente = 2356&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Si analiza la tabla AUDIT verá que las sentencias ejecutadas accionaron los disparadores que se habían creado. Éstos insertaron un registro por cada modificación efectuada a la tabla. Verifique con la siguiente consulta:&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;select * from AUDIT&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-size:100%;"&gt;&lt;strong&gt;Optimización 1: Creación de un Procedimiento Almacenado que automatice la instalación del sistema&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Con lo visto hasta el momento, ya esta en condiciones de crear sus propios disparadores sobre cada tabla que desee auditar, ahora suponga que quiere auditar con este método la base de datos corporativa, en la cual se alojan datos referentes a todos los subsistemas de la organización. Suponga que las tablas a auditar son 300.&lt;br /&gt;&lt;br /&gt;¿No le interesa la idea de automatizar la instalación de los disparadores?&lt;br /&gt;&lt;br /&gt;Lo que se presenta a continuación es un procedimiento almacenado que encapsula la creación de los tres disparadores para una tabla determinada que es pasada como parámetro de entrada:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;CREATE PROCEDURE sp_instalarTrigger &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;@tabla as char(128)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;as&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;-- Aseguramos que el parametro de entrada esté libre de espacios&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;set @tabla = ltrim(rtrim(@tabla))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;-- Creacion del trigger para ELIMINACION&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;EXECUTE ('CREATE TRIGGER AUDITdel_' + @tabla +' ON ' + @tabla +' FOR DELETE AS Insert into AUDITselect "Delete", getdate(), "Eliminacion de un registro", SYSTEM_USER, host_name(),APP_NAME()')&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;-- Creacion del trigger para INSERCIONEXECUTE ('CREATE TRIGGER AUDITins_' + @tabla +' ON ' + @tabla +' FOR INSERT AS Insert into AUDITselect "Insert", getdate(), "Insercion de un registro", SYSTEM_USER, host_name(),APP_NAME()')&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;-- Creacion del trigger para MODIFICACIONEXECUTE ('CREATE TRIGGER AUDITupd_' + @tabla +' ON ' + @tabla +' FOR UPDATE AS Insert into AUDITselect "Update", getdate(), "Modificacion de un registro", SYSTEM_USER, host_name(),APP_NAME()')&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Por ejemplo, para nuestra tabla sería:&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;Exec sp_instalarTrigger "CLIENTES"&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Un segundo paso para lograr una instalación totalmente automática podría ser crear otro procedimiento almacenado que:&lt;br /&gt;Cargue un cursor con todas las tablas de usuario a través de un select de la tabla sysobjects (con la condición de que el campo xtype sea "U").&lt;br /&gt;Luego ejecute el procedimiento almacenado antes creado por cada registro dentro del cursor.&lt;br /&gt;Sin embargo debe ser cuidadoso con este enfoque de "instalación masiva" porque la sobrecarga de procesamiento que existirá en el servidor a partir de la instalación de los disparadores podría no ser aceptable (o deseable) en todas la tablas, mas adelante hablaremos de ello.&lt;br /&gt;Optimización 2: Personalización del mensaje grabado (tablas inserted y deleted)Si analiza la tabla AUDIT que se ha propuesto verá que uno de los campos es "descripción". Hasta el momento hemos estado insertando una cadena fija que presenta una suerte de descripción acerca del evento que dispara el disparador, en realidad ese campo está pensado para guardar una descripción detallada del evento ocurrido, ¿cuán detallada?, todo lo detallada que el usuario considere necesario y esté dispuesto a programar.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;A continuación desarrollamos un ejemplo práctico para el disparador que controla la eliminación, en donde utilizamos la tabla "deleted", ésta es una tabla temporal a la cual se puede tener acceso dentro de un disparador, de este modo podemos recuperar los datos que están siendo eliminados y que han disparado la ejecución del disparador (nótese que también se podría utilizar la tabla temporal inserted accesible desde los disparadores para inserción).&lt;br /&gt;Lo que vamos a hacer es recuperar el nombre del cliente que se está eliminando y lo vamos a guardar en el campo descripción de la tabla AUDIT.&lt;br /&gt;De este modo reformulamos la creación del disparador de la siguiente manera:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;-- Primero eliminamos el trigger que se habia creado anteriormente&lt;/div&gt;&lt;div&gt;if exists (select * from sysobjects where id =object_id(N'[dbo].[AUDITdel_CLIENTES]') and OBJECTPROPERTY(id, N'IsTrigger') = 1)&lt;/div&gt;&lt;div&gt;drop trigger [dbo].[AUDITdel_CLIENTES]&lt;/div&gt;&lt;div&gt;GO&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;-- Luego creamos el trigger para ELIMINACION con el mensaje personalizado&lt;/div&gt;&lt;div&gt;CREATE TRIGGER AUDITdel_CLIENTES ON CLIENTES FOR DELETE &lt;/div&gt;&lt;div&gt;AS&lt;/div&gt;&lt;div&gt;Insert into AUDIT select "Delete",getdate(), (select "Nombre eliminado: " + convert(char(15),Nombre) from deleted) ,SYSTEM_USER, host_name(),APP_NAME()&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Con este enfoque se podrían almacenar todos los datos antes de una eliminación para poder reconstruir un registro si es necesario, aunque si su aplicación hace eliminaciones masivas (es decir de mas de dos registros), debería replantear la inserción a la tabla AUDIT recorriendo, por ejemplo, un cursor armado con una consulta a la tabla deleted.Aquí cabe una aclaración: el código Transact-SQL que se encuentra dentro del trigger puede estar compuesto por bucles, condicionales, casi cualquier construcción Transact-SQL válida, sin embargo deberá tener en cuenta que toda la demora que introduzca este código impactará directamente en cada una de las operaciones que los usuarios hagan sobre los datos.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="font-size:100%;"&gt;Encriptación de los disparadores&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;&lt;span style="font-size:100%;"&gt;&lt;/span&gt;&lt;/strong&gt; &lt;/div&gt;&lt;div&gt;Una consideración que podría ser importante es la de encriptar el código de los disparadores creados, de este modo evitará que se conozca cómo y donde se está almacenando la información de auditoria, mas allá de que luego restrinja el acceso a la tabla AUDIT. El código debe ser encriptado al momento de la creación del disparador a través de la cláusula "WITH ENCRYPTION", para nuestro disparador de eliminación sería:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;-- Primero eliminamos el trigger que se había creado anteriormente&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;if exists (select * from sysobjects where id =object_id(N'[dbo].[AUDITdel_CLIENTES]') and OBJECTPROPERTY(id, N'IsTrigger') = 1)drop trigger [dbo].[AUDITdel_CLIENTES]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;GO&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:Courier New;"&gt;&lt;/span&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;-- Luego creamos el trigger para ELIMINACION con el mensaje personalizado y ENCRIPTADO&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;CREATE TRIGGER AUDITdel_CLIENTES WITH ENCRYPTION ON CLIENTES FOR DELETE &lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;AS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family:courier new;"&gt;Insert into AUDIT select "Delete",Getdate(), (select "Nombre eliminado: " + convert(char(15),Nombre) from deleted) ,SYSTEM_USER, host_name(),APP_NAME()&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;span style="font-size:100%;"&gt;&lt;strong&gt;Conclusiones&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;Hemos visto como, a partir de unas simples sentencias de Transact-SQL , se puede crear todo un sistema que realice un seguimiento muy detallado de las operaciones de los usuarios sobre los datos. Además vimos que su diseño es flexible y puede ser personalizado para necesidades específicas.&lt;br /&gt;Si desea, puede completar el desarrollo creando una interfaz gráfica que acceda a la tabla AUDIT y permita hacer consultas con filtros configurables por el usuario, emitir reportes, etc.&lt;br /&gt; &lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7580367085552365646-7690414847099744733?l=mminoli.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mminoli.blogspot.com/feeds/7690414847099744733/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7580367085552365646&amp;postID=7690414847099744733' title='5 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7580367085552365646/posts/default/7690414847099744733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7580367085552365646/posts/default/7690414847099744733'/><link rel='alternate' type='text/html' href='http://mminoli.blogspot.com/2007/09/construccin-de-un-sistema-de-auditora.html' title='Construcción de un sistema de auditoría con triggers en Microsoft SQL Server'/><author><name>Mariano Minoli</name><uri>http://www.blogger.com/profile/08353467425964487192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_sdjuoL-kiAQ/Rv6MsPAn4bI/AAAAAAAAAAM/Sgqn_CZo4ps/s72-c/TablaAuditoria.gif' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7580367085552365646.post-2094869503676119878</id><published>2007-08-31T14:46:00.000-07:00</published><updated>2007-09-01T11:34:27.398-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><title type='text'>Se viene SQL Server 2008 !</title><content type='html'>Se hemos visto últimamente en distintos lugares, la versión 2008 de SQL Server estará disponible para el primer cuatrimestre del año que viene.&lt;br /&gt;&lt;br /&gt;Aqui van algunas urls relacionadas al tema:&lt;br /&gt;&lt;a href="http://www.microsoft.com/sql/prodinfo/futureversion/default.mspx"&gt;http://www.microsoft.com/sql/prodinfo/futureversion/default.mspx&lt;/a&gt;&lt;br /&gt;&lt;a href="https://connect.microsoft.com/SQLServer/content/content.aspx?ContentID=5395"&gt;https://connect.microsoft.com/SQLServer/content/content.aspx?ContentID=5395&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Esperamos con ansiedad la nueva versión !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7580367085552365646-2094869503676119878?l=mminoli.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mminoli.blogspot.com/feeds/2094869503676119878/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7580367085552365646&amp;postID=2094869503676119878' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7580367085552365646/posts/default/2094869503676119878'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7580367085552365646/posts/default/2094869503676119878'/><link rel='alternate' type='text/html' href='http://mminoli.blogspot.com/2007/08/se-viene-sql-server-2008.html' title='Se viene SQL Server 2008 !'/><author><name>Mariano Minoli</name><uri>http://www.blogger.com/profile/08353467425964487192</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
