Seguimiento de cambios en una base de datos de SQL Server 2005
-
08-07-2019 - |
Pregunta
Me encargaron desarrollar una solución que rastrea los cambios en una base de datos.
Para las actualizaciones que necesito capturar:
- fecha de actualización
- valor anterior
- nuevo valor
- campo afectado
- persona haciendo cambio
- ID de registro
- el registro de la tabla está en
Para eliminaciones:
- fecha de eliminación
- persona haciendo borrar
- El título / descripción / id del registro eliminado. Las tablas en las que estoy rastreando los cambios tienen un campo de título o descripción. Me gustaría capturar esto antes de que se elimine el registro.
- el registro de la tabla estaba en
Para inserciones:
- fecha de inserción
- persona haciendo cambio
- ID de registro
- el registro de la tabla está en
He pensado en algunas formas de hacer esto:
- Estoy usando procedimientos almacenados para cualquier actualización / eliminación / inserción. Crearía un & Quot genérico; seguimiento & Quot; mesa. Tendría suficientes campos para capturar todos los datos. Luego agregaría otra línea en cada proceso almacenado en el efecto de & Quot; Insertar registro en la tabla de seguimiento & Quot ;.
- inconveniente: todas las actualizaciones / eliminaciones / inserciones están todas mezcladas en la misma tabla
- muchos campos NULADOS
- ¿cómo puedo rastrear actualizaciones / eliminaciones / insertos por lotes? < ---- esto podría no ser un problema. Realmente no hago nada como esto en la aplicación.
- ¿Cómo capturo al usuario que realiza la actualización? La base de datos solo ve una cuenta.
- edite una gran cantidad de código existente para editar.
- Por último, podría crear un activador que se llama después de actualizaciones / eliminaciones / inserciones. Muchas de las mismas desventajas que la primera solución, excepto: tendría que editar tanto código. No estoy seguro de cómo rastrear las actualizaciones. No parece que haya una forma de utilizar los disparadores para ver los registros actualizados recientemente.
Estoy usando asp.net, C #, sql server 2005, iis6, windows 2003. No tengo presupuesto, así que lamentablemente no puedo comprar nada que me ayude con esto.
¡Gracias por sus respuestas!
Solución
Un disparador no tendría toda la información que necesita por varias razones, pero ninguna identificación de usuario es el factor decisivo.
Yo diría que estás en el camino correcto con un sp común para insertar donde sea que se haga un cambio. Si está estandarizando los sp para sus interfaces, entonces está por delante del juego: será difícil infiltrarse en un cambio que no se rastrea.
Mire esto como el equivalente de una pista de auditoría en una aplicación de contabilidad, este es el Diario, una sola tabla con cada transacción registrada. No implementarían diarios separados para depósitos, retiros, ajustes, etc. y este es el mismo principio.
Otros consejos
Odio eludir el problema y sé que no tiene presupuesto, pero la solución más simple será actualizar a SQL Server 2008. Tiene esta característica integrado en . Pensé que al menos debería mencionarse para cualquier otra persona que se encuentre con esta pregunta, incluso si no puede usarla usted mismo.
(Entre las ediciones desplegables de SQL 2008, esta característica solo está disponible en Enterprise).
Te sugiero que uses 2 columnas en cada tabla. nombra rowhistory e IsDeleted y el tipo de datos será xml y bit. Nunca elimine las filas, utilice siempre el indicador IsDeleted Ahora ve con disparadores de actualización. Te daré ejemplo para lo mismo Tengo esta tabla llamada Página
CREATE TABLE te_Page([Id] [int] IDENTITY(1,1) NOT NULL, [Name] [varchar](200) NOT NULL, [Description] [varchar](200) NULL,[CreatedBy] [uniqueidentifier] NULL, [CreatedDate] [datetime] NOT NULL, [UpdatedBy] [uniqueidentifier] NULL, [UpdatedDate] [datetime] NULL, [IsDeleted] [bit] NULL, [RowHistory] [xml] NULL, CONSTRAINT [PK_tm_Page] PRIMARY KEY CLUSTERED ([Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
Ahora, después de crear la tabla, todo lo que necesita hacer es copiar y pegar el siguiente código y su tarea estará lista para la tabla de páginas. Comenzará a registrar el historial de la fila en la misma fila que se actualiza junto con los valores antiguos y nuevos.
ALTER Trigger [dbo].[Trg_Te_Page]
On [dbo].[te_Page]
After Update
As
--If @@rowcount = 0 Or Update(RowHistory)
--Return
Declare @xml NVARCHAR(MAX)
Declare @currentxml NVARCHAR(MAX)
Declare @node NVARCHAR(MAX)
Declare @ishistoryexists XML
Declare @FormLineAttributeValueId int
-- new Values
Declare @new_Name varchar(200)
Declare @new_Description varchar(200)
Declare @new_CreatedBy UNIQUEIDENTIFIER
Declare @new_CreatedDate DATETIME
Declare @new_UpdatedBy UNIQUEIDENTIFIER
Declare @new_UpdatedDate DATETIME
Declare @new_IsDeleted BIT
--old values
Declare @old_Name varchar(200)
Declare @old_Description varchar(200)
Declare @old_CreatedBy UNIQUEIDENTIFIER
Declare @old_CreatedDate DATETIME
Declare @old_UpdatedBy UNIQUEIDENTIFIER
Declare @old_UpdatedDate DATETIME
Declare @old_IsDeleted BIT
-- declare temp fmId
Declare @fmId int
-- declare cursor
DECLARE curFormId cursor
FOR select Id from INSERTED
-- open cursor
OPEN curFormId
-- fetch row
FETCH NEXT FROM curFormId INTO @fmId
WHILE @@FETCH_STATUS = 0
BEGIN
Select
@FormLineAttributeValueId = Id,
@old_Name = Name,
@old_Description = [Description],
@old_CreatedBy = CreatedBy,
@old_CreatedDate =CreatedDate,
@old_UpdatedBy =UpdatedBy,
@old_UpdatedDate =UpdatedDate,
@old_IsDeleted = IsDeleted,
@currentxml = cast(RowHistory as NVARCHAR(MAX))
From DELETED where Id=@fmId
Select
@new_Name = Name,
@new_Description = [Description],
@new_CreatedBy = CreatedBy,
@new_CreatedDate =CreatedDate,
@new_UpdatedBy =UpdatedBy,
@new_UpdatedDate =UpdatedDate,
@new_IsDeleted = IsDeleted
From INSERTED where Id=@fmId
set @old_Name = Replace(@old_Name,'&','&')
set @old_Name = Replace(@old_Name,'>','>')
set @old_Name = Replace(@old_Name,'<','<')
set @old_Name = Replace(@old_Name,'"','"')
set @old_Name = Replace(@old_Name,'''',''')
set @new_Name = Replace(@new_Name,'&','&')
set @new_Name = Replace(@new_Name,'>','>')
set @new_Name = Replace(@new_Name,'<','<')
set @new_Name = Replace(@new_Name,'"','"')
set @new_Name = Replace(@new_Name,'''',''')
set @old_Description = Replace(@old_Description,'&','&')
set @old_Description = Replace(@old_Description,'>','>')
set @old_Description = Replace(@old_Description,'<','<')
set @old_Description = Replace(@old_Description,'"','"')
set @old_Description = Replace(@old_Description,'''',''')
set @new_Description = Replace(@new_Description,'&','&')
set @new_Description = Replace(@new_Description,'>','>')
set @new_Description = Replace(@new_Description,'<','<')
set @new_Description = Replace(@new_Description,'"','"')
set @new_Description = Replace(@new_Description,'''',''')
set @xml = ''
BEGIN
-- for Name
If ltrim(rtrim(IsNull(@new_Name,''))) != ltrim(rtrim(IsNull(@old_Name,'')))
set @xml = @xml + '<ColumnInfo ColumnName="Name" OldValue="'+ @old_Name + '" NewValue="' + @new_Name + '"/>'
-- for Description
If ltrim(rtrim(IsNull(@new_Description,''))) != ltrim(rtrim(IsNull(@old_Description,'')))
set @xml = @xml + '<ColumnInfo ColumnName="Description" OldValue="'+ @old_Description + '" NewValue="' + @new_Description + '"/>'
-- CreatedDate
If IsNull(@new_CreatedDate,'') != IsNull(@old_CreatedDate,'')
set @xml = @xml + '<ColumnInfo ColumnName="CreatedDate" OldValue="'+ cast(isnull(@old_CreatedDate,'') as varchar(100)) + '" NewValue="' + cast(isnull(@new_CreatedDate,'') as varchar(100)) + '"/>'
-- CreatedBy
If cast(IsNull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar (36)) != cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar(36))
set @xml = @xml + '<ColumnInfo ColumnName="CreatedBy" OldValue="'+ cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(isnull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
'"/>'
-- UpdatedDate
If IsNull(@new_UpdatedDate,'') != IsNull(@old_UpdatedDate,'')
set @xml = @xml + '<ColumnInfo ColumnName="UpdatedDate" OldValue="'+ cast(IsNull(@old_UpdatedDate,'') as varchar(100)) + '" NewValue="' + cast(IsNull(@new_UpdatedDate,'') as varchar(100)) + '"/>'
-- UpdatedBy
If cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) != cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))
set @xml = @xml + '<ColumnInfo ColumnName="UpdatedBy" OldValue="'+ cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
'"/>'
-- IsDeleted
If cast(IsNull(@new_IsDeleted,'') as varchar(10)) != cast(IsNull(@old_IsDeleted,'') as varchar(10))
set @xml = @xml + '<ColumnInfo ColumnName="IsDeleted" OldValue="'+ cast(IsNull(@old_IsDeleted,'') as varchar(10)) + '" NewValue="' + cast(IsNull(@new_IsDeleted,'') as varchar(10)) + '" />'
END
Set @xml = '<RowInfo TableName="te_Page" UpdatedBy="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(50)) + '" UpdatedDate="' + Convert(Varchar(20),GetDate()) + '">' + @xml + '</RowInfo>'
Select @ishistoryexists = RowHistory From DELETED
--print @ishistoryexists
If @ishistoryexists is null
Begin
Set @xml = '<History>' + @xml + '</History>'
Update te_Page
Set
RowHistory = @xml
Where
Id = @FormLineAttributeValueId
End
Else
Begin
set @xml = REPLACE(@currentxml, '<History>', '<History>' + @xml)
Update te_Page
Set
RowHistory = @xml
Where
Id = @FormLineAttributeValueId
End
FETCH NEXT FROM curFormId INTO @fmId
END
CLOSE curFormId
DEALLOCATE curFormId
Ahora, cada vez que realice una actualización, sus datos se almacenarán en la filahistoria columna
Una forma en que he visto esto manejado (aunque sinceramente no lo recomendaría) es manejarlo a través de procedimientos almacenados, pasando el ID de usuario / nombre de usuario / lo que sea como parámetro. Los procedimientos almacenados llamarían un procedimiento de registro, que escribió los detalles relevantes en una tabla de registro central.
Aquí es donde se puso un poco loco, aunque ...
Para INSERT / UPDATE, las filas relevantes se almacenaron en la tabla como datos XML una vez que INSERT / UPDATE se completó con éxito. Para DELETEs, la fila se almacenó antes de la ejecución de DELETE (aunque, de manera realista, podrían haberla obtenido de la salida de la declaración DELETE, al menos con SQL Server 2005).
Si no recuerdo mal, la tabla solo tenía un par de columnas: UserID, DateTime of the logging, Transaction Type (I / U / D), datos XML que contienen las filas relevantes, el nombre de la tabla y el valor de la clave primaria (utilizado principalmente para una búsqueda rápida de los registros que querían).
Muchas formas de pelar un gato, sin embargo ...
Mi consejo es que mantener es simple. Expanda más adelante si / cuando lo necesite.
Si tiene la capacidad de hacerlo, bloquee a los usuarios para que solo puedan realizar declaraciones procesables en las tablas a través de procedimientos almacenados y luego maneje el registro (como quiera) a partir de allí.
creamos el nuestro y solo necesitábamos que el usuario y la PC pasaran a cada procedimiento almacenado de agregar / actualizar. entonces es solo una cuestión de obtener el registro original y llenar las variables y compararlas con las variables pasadas y registrar los datos en nuestra tabla. para las eliminaciones, solo tenemos una copia de las tablas de origen + un campo de marca de tiempo para que el registro nunca se elimine realmente y se pueda restaurar en cualquier momento que lo necesitemos (obviamente, la rutina de eliminación verifica las relaciones FK y demás).
agregar / actualizar la tabla de registro se ve así fecha y hora, nombre de la tabla, nombre_columna, record_id, valor antiguo, nuevo valor, user_id, computadora
nunca insertamos nulos, por lo que los convertimos en cadenas vacías, las nuevas entradas están marcadas con '{nueva entrada}' en la columna old_value. record_id está formado por tantas columnas clave para identificar de forma única ese registro único (campo1 + '.' + campo2 + ...)
En primer lugar, en todas sus tablas debe tener al menos estas columnas agregadas a las columnas de datos DateCreated, UserCreated, DateModified, UserModified. Posiblemente desee agregar un & Quot; Estado & Quot; o " LastAction " columna para que nunca elimine una fila, simplemente configúrela en un estado eliminado / insertado / actualizado. A continuación, puede crear una & Quot; Tabla de historial & Quot; que es una copia exacta de la primera tabla. Luego, en cualquier actualización o eliminación, haga que el activador copie las entradas de la tabla Eliminada en la tabla Historial cambiando los campos DateModified, UserModified y Status al mismo tiempo.
He tenido una configuración en SQL Server donde usaríamos vistas para acceder a nuestros datos, lo que manejaría las inserciones, actualizaciones y eliminaciones con desencadenadores INSTEAD OF.
Por ejemplo: un disparador INSTEAD OF DELETE en la vista marcaría los registros en la tabla subyacente como eliminados, y la vista se filtró para no mostrar registros eliminados.
En todos los disparadores, actualizamos una fecha de modificación y un nombre de usuario. El problema es que registra el nombre de usuario de la base de datos, que no es lo mismo que el nombre de usuario final de la aplicación.
La vista debe estar vinculada al esquema para que esto funcione.
Acerca del registro de usuarios que cambian la base de datos: puede crear tantos usuarios de SQL como necesite para su base de datos y si usa sesiones y acceso restringido / registrado a su programa / script puede usar esa información para iniciar diferentes configuraciones de conexión de base de datos (es decir, nombre de usuario), antes de cualquier operación con DB.
Al menos eso debería ser factible para scripts PHP-sabios, pero podría estar equivocado para asp.net.