sql >> Base de Datos >  >> RDS >> Database

Realización de una auditoría de cambios de datos mediante una tabla temporal

SQL Server 2016 ha introducido una función llamada "Tabla temporal versionada del sistema". Usando una tabla normal, puede recuperar datos actuales; mientras usa una tabla temporal versionada por el sistema, puede recuperar datos que se eliminaron o actualizaron en el pasado. Para hacer eso, una tabla temporal creará una tabla de historial. La tabla de historial almacenará datos antiguos con "start_time ” y “end_time ”. Lo que indica un período de tiempo durante el cual el registro estuvo activo.

Ejemplo:si actualiza el precio de un producto de 30 a 50 consultando una tabla normal, puede recuperar el precio del producto actualizado que es 50. Usando una tabla temporal, puede recuperar el valor anterior que es 30.

Usando tablas temporales, uno puede realizar:

  1. Historial de seguimiento de un registro :podemos revisar un valor del registro específico, que ha cambiado con el tiempo.
  2. Recuperación de nivel de registro :si eliminamos un registro específico de la tabla o un registro está dañado, podemos recuperarlo de la tabla de historial.

Las tablas temporales capturan la fecha y la hora de un registro en función de las fechas físicas (fecha del calendario) de la actualización y eliminación del registro. Actualmente, no admite el control de versiones en función de las fechas lógicas. Por ejemplo, si actualiza el nombre del producto mediante la instrucción UPDATE a la 1:00 p. m., la tabla temporal mantendrá el historial del nombre del producto hasta la 1:00 p. m. Después de eso, se aplicará un nuevo nombre. Sin embargo, ¿qué sucede si el cambio de nombre del producto debía comenzar a partir de las 2:00 p. m.? Esto significa que debe actualizar la instrucción a tiempo perfectamente para que funcione y debería haber ejecutado la instrucción ACTUALIZAR a las 2:00 p. m. en lugar de a la 1:00 p. m.

Las tablas temporales tienen los siguientes requisitos previos:

  1. Se debe definir una clave principal.
  2. Se deben definir dos columnas para registrar la hora de inicio y la hora de finalización con el tipo de datos datetime2. Estas columnas se denominan columnas SYSTEM_TIME.

También tienen algunas limitaciones:

  1. Los activadores INSTEAD OF y OLTP en memoria no están permitidos.
  2. Las tablas de historial no pueden tener ninguna restricción.
  3. Los datos en la tabla de historial no se pueden modificar.

Crear una tabla versionada por el sistema

El siguiente script se usará para crear una tabla versionada por el sistema simple:

Use DemoDatabase
Go
CREATE TABLE dbo.Prodcuts
	(
	      Product_ID int identity (1,1) primary key
	    , Product_Name varchar (500)
	    , Product_Cost int
	    , Quantity int
	    , Product_Valid_From datetime2 GENERATED ALWAYS AS ROW START NOT NULL
	    , Product_Valid_TO datetime2 GENERATED ALWAYS AS ROW END NOT NULL
	    , PERIOD FOR SYSTEM_TIME (Product_Valid_From,Product_Valid_TO)
	)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE =dbo.Product_Change_History));

En el script anterior, he definido HISTORY_TABLE llamado dbo. Historial_de_cambios_del_producto. Si no especifica un nombre para la tabla de historial, SQL Server creará automáticamente una tabla de historial con la siguiente estructura.

Dbo.MSSQL_TemporalHistoryFor_xxx, donde xxx es el ID del objeto.

La tabla temporal se verá como se muestra en la siguiente captura de pantalla:

¿Cómo se actualizarán las columnas de período al ejecutar la instrucción DML en la tabla temporal?

Cada vez que ejecutamos insertar, actualizar y eliminar una consulta en la tabla temporal, las columnas de período (SysStartDate y SysEndDate) se actualizarán.

Insertar consulta

Cuando realizamos la operación INSERT en la tabla temporal, el sistema establece el valor de la columna SysStartTime en la hora de inicio de la transacción actual y marca la fila como abierta.

Insertemos algunas filas en los 'Productos ' y revise cómo se almacenan los datos en esta tabla.

INSERT INTO prodcuts 
            (product_name, 
             product_cost, 
             quantity) 
VALUES      ( 'Mouse', 
              500, 
              10 ), 
            ( 'Key-Board', 
              200, 
              5 ), 
            ( 'Headset', 
              500, 
              1 ), 
            ( 'Laptop', 
              50000, 
              1 )
 select * from Prodcuts

Como se muestra en la captura de pantalla anterior, el valor de 'Product_Valid_From ' columna es '2018-04-02 06:55:04.4865670 ' que es la fecha de inserción de la fila. Y el valor del 'Product_Valid_To ' columna es '9999-12-31 23:59:59.9999999 ', lo que indica que la fila está abierta.

Consulta de actualización

Cuando ejecutamos cualquier consulta de actualización en la tabla temporal, el sistema almacenará los valores de fila anteriores en la tabla de historial y establecerá el tiempo de transacción actual como EndTime y actualice la tabla actual con un nuevo valor. Hora de inicio del sistema será la hora de inicio de la transacción y SysEndTime será el máximo de 9999-12-31.

Cambiemos el costo del producto de 'Ratón ' de 500 a 250. Comprobaremos el resultado de 'Producto '.

Begin tran UpdatePrice
Update Prodcuts set Product_cost=200 where Product_name='Mouse'
Commit tran UpdatePrice

select * from Prodcuts where Product_name='Mouse'

Como puede ver en la captura de pantalla anterior, un valor de 'Product_Valid_From La columna ' ha sido cambiada. El nuevo valor es el tiempo de transacción actual (UTC). Y el valor del 'Product_Valid_To ' columna es '9999-12-31 23:59:59.9999999 ', que indica que la fila está abierta y ha actualizado el precio.

Observemos el resultado del Product_change_history consultando la tabla.

select * from Product_Change_History where Product_name='Mouse'

Como puede ver en la captura de pantalla anterior, se ha agregado una fila en Product_change_history tabla, que tiene una versión antigua de la fila. Valor de 'Product_cost ' es 500, Valor de 'Product_valid_From ' es la hora en que se insertó el registro y el valor de Product_Valid_To columna es cuando el valor de la columna Product_cost fue actualizado. Esta versión de fila se considera cerrada.

Eliminar Consulta

Cuando eliminamos un registro de la tabla temporal, el sistema almacenará la versión actual de la fila en la tabla de historial y establecerá el tiempo de transacción actual como EndTime y eliminará el registro de la tabla actual.

Eliminemos el registro de 'Auriculares'.

Begin tran DeletePrice
    delete from Prodcuts where product_name='Headset'
Commit tran DeletePrice

Observemos el resultado de Product_change_history consultando la tabla.

select * from Product_Change_History where Product_name='Headset'

Como puede ver en la captura de pantalla anterior, se agregó una fila en Product_change_history tabla, que se eliminó de la tabla actual. Valor de 'Product_valid_From ’ es la hora en que se insertó el registro y el valor de Product_Valid_To la columna es la hora en que se eliminó la fila, lo que indica que la versión de la fila está cerrada.

Auditoría de cambios de datos durante un tiempo específico

Para auditar los cambios de datos de una tabla específica, debemos realizar el análisis basado en el tiempo de las tablas temporales. Para hacer eso, debemos usar el 'FOR SYSTEM_TIME ' con subcláusulas temporales específicas a continuación para los datos de consulta en las tablas actual e histórica. Permítanme explicar el resultado de las consultas utilizando diferentes subcláusulas. A continuación se muestra la configuración:

  1. Inserté un producto llamado "Flat Washer 8" con precio de lista 0,00 en la tabla temporal a las 09:02:25 a. m.
  2. Cambié el precio de lista a las 10:13:56 a. m. El nuevo precio es de 500,00.

A PARTIR DE

Esta cláusula se utilizará para recuperar el estado de los registros durante un tiempo determinado en el A PARTIR DE subcláusula. Para entenderlo, ejecutemos varias consultas:

Primero, ejecutaremos una consulta usando el A PARTIR DE cláusula con ”SystemTime =10:15:59 ”.

select Name, ListPrice,rowguid,Product_Valid_From,Product_Valid_TO from DemoDatabase.dbo.tblProduct  FOR system_time as of '2018-04-20 10:15:56
where name ='Flat Washer 8'

Ahora, como puede ver en la captura de pantalla anterior, la consulta devolvió una fila con el valor actualizado de "Precio de lista ” y valor de Product_Valid_To es el máximo de fecha.

Ejecutemos otra consulta usando el A PARTIR DE c lause con “SystemTime =09:10:56: ”.

Ahora, como puede ver en la captura de pantalla anterior, el valor de "ListPrice ” es 0,00.

De a

Esta cláusula devolverá las filas activas entre y . Para entenderlo, ejecutemos la siguiente consulta usando el From..To subcláusula con "SystemTime From '2018-04-20 09:02:25' to '2018-04-20 10:14:56 ‘”.

select Name, ListPrice,rowguid,Product_Valid_From,Product_Valid_TO,ListPrice from DemoDatabase.dbo.tblProduct  FOR system_time from '2018-04-20 09:02:25 to '2018-04-20 10:13:56 where name =  'Flat Washer 8'

La siguiente captura de pantalla muestra el resultado de la consulta:

ENTRE y

Esta cláusula es similar a FROM.. A cláusula. La única diferencia es que incluirá los registros que estaban activos en . Para entenderlo, ejecutemos la siguiente consulta:

select Name, ListPrice,rowguid,Product_Valid_From,Product_Valid_TO,ListPrice from DemoDatabase.dbo.tblProduct  FOR system_time between '2018-04-20 09:02:25.1265684' and '2018-04-20 10:13:56.1265684' where name =  'Flat Washer 8'

La siguiente captura de pantalla muestra el resultado de la consulta:

Incluido EN (, )

Esta subcláusula incluirá los registros que se activaron y terminaron dentro del rango de fechas especificado. No incluye los registros activos. Para entenderlo, ejecute la siguiente consulta usando “Contenido EN ‘2018-04-20 09:02:25 ' a '2018-04-20 10:14:56'

select Name, ListPrice,rowguid,Product_Valid_From,Product_Valid_TO,ListPrice from DemoDatabase.dbo.tblProduct  FOR system_time Contained IN( '2018-04-20 09:02:25' , '2018-04-20 10:13:56 ') where name =  'Flat Washer 8'

La siguiente captura de pantalla muestra el resultado de la consulta:

Escenario

Una organización está utilizando un software de inventario. Ese software de inventario utiliza una tabla de productos que es una tabla temporal de versiones del sistema. Debido a un error en la aplicación, se eliminaron algunos productos y los precios de los productos también se actualizaron incorrectamente.

Como administradores de bases de datos, debemos investigar este problema y recuperar los datos que se actualizaron incorrectamente y se eliminaron de la tabla.

Para simular el escenario anterior, creemos una tabla con algunos datos significativos. Voy a crear una nueva tabla temporal llamada 'tblProduct ' en la base de datos de demostración, que es un clon de [Producción].[Productos] tabla de la base de datos AdventureWorks2014.

Para realizar la tarea anterior, he seguido los pasos a continuación:

  1. Extraído "crear secuencia de comandos de tabla" [Producción]. [Productos] de la base de datos AdventureWorks2014.
  2. Se eliminaron todas las "restricciones e índices" del script.
  3. Mantuvo la estructura de la columna sin cambios.
  4. Para convertirlo en una tabla temporal, agregué las columnas SysStartTime y SysEndTime.
  5. Habilitado System_Versioning.
  6. Tabla de historial especificada.
  7. Ejecutó el script en la base de datos edemo.

A continuación se muestra el guión:

USE [DemoDatabase]
GO
CREATE TABLE [tblProduct](
	[ProductID] [int] IDENTITY(1,1) Primary Key,
	[Name] varchar(500) NOT NULL,
	[ProductNumber] [nvarchar](25) NOT NULL,
	[Color] [nvarchar](15) NULL,
	[SafetyStockLevel] [smallint] NOT NULL,
	[ReorderPoint] [smallint] NOT NULL,
	[StandardCost] [money] NOT NULL,
	[ListPrice] [money] NOT NULL,
	[Size] [nvarchar](5) NULL,
	[SizeUnitMeasureCode] [nchar](3) NULL,
	[WeightUnitMeasureCode] [nchar](3) NULL,
	[Weight] [decimal](8, 2) NULL,
	[DaysToManufacture] [int] NOT NULL,
	[ProductLine] [nchar](2) NULL,
	[Class] [nchar](2) NULL,
	[Style] [nchar](2) NULL,
	[ProductSubcategoryID] [int] NULL,
	[ProductModelID] [int] NULL,
	[SellStartDate] [datetime] NOT NULL,
	[SellEndDate] [datetime] NULL,
	[DiscontinuedDate] [datetime] NULL,
	[rowguid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
	[ModifiedDate] [datetime] NOT NULL,
	Product_Valid_From datetime2 GENERATED ALWAYS AS ROW START NOT NULL
    , Product_Valid_TO datetime2 GENERATED ALWAYS AS ROW END NOT NULL
    , PERIOD FOR SYSTEM_TIME (Product_Valid_From,Product_Valid_TO)
 )
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE =dbo.Product_History));
GO

Importé datos de la tabla de productos de la base de datos "AdventureWorks2014" a la tabla de productos de "DemoDatabase" ejecutando el siguiente script:

insert into DemoDatabase.dbo.tblProduct
(Name
,ProductNumber
,Color
,SafetyStockLevel
,ReorderPoint
,StandardCost
,ListPrice
,Size
,SizeUnitMeasureCode
,WeightUnitMeasureCode
,Weight
,DaysToManufacture
,ProductLine
,Class
,Style
,ProductSubcategoryID
,ProductModelID
,SellStartDate
,SellEndDate
,DiscontinuedDate
,rowguid
,ModifiedDate)
select top 50
Name
,ProductNumber
,Color
,SafetyStockLevel
,ReorderPoint
,StandardCost
,ListPrice
,Size
,SizeUnitMeasureCode
,WeightUnitMeasureCode
,Weight
,DaysToManufacture
,ProductLine
,Class
,Style
,ProductSubcategoryID
,ProductModelID
,SellStartDate
,SellEndDate
,DiscontinuedDate
,rowguid
,ModifiedDate
from AdventureWorks2014.Production.Product

Eliminé los registros de nombre de producto que comienzan con "Tuerca hexagonal de mermelada delgada" de tblProduct. También cambié el precio de los productos cuyos nombres comienzan con Flat Washer en 'tblProduct ’ tabla ejecutando la siguiente consulta:

delete from DemoDatabase.dbo.Product where name like '%Thin-Jam Hex Nut%'
waitfor delay '00:01:00'
update DemoDatabase.dbo.tblProduct set ListPrice=500.00 where name like '%Flat Washer%'

Somos conscientes del momento en que se eliminaron los datos. Por lo tanto, para identificar qué datos se han eliminado, utilizaremos la subcláusula Contenida-EN. Como mencioné anteriormente, me dará la lista de registros que tienen versiones de fila que se activaron y finalizaron dentro del rango de fechas especificado. Luego, ejecutado debajo de la consulta:

declare @StartDateTime datetime
declare @EndDateTime datetime
set @StartDateTime=convert (datetime2, getdate()-1)
set @EndDateTime=convert (datetime2, getdate())
select ProductID, Name, ProductNumber,Product_Valid_From, Product_Valid_To from Product For SYSTEM_TIME Contained IN ( @StartDateTime , @EndDateTime)

Al ejecutar la consulta anterior, se recuperaron 22 filas.

El contenido-IN La cláusula completará las filas que se actualizaron y eliminaron durante el tiempo dado.

Rellenar registros eliminados:

Para completar los registros eliminados, debemos omitir los registros que se actualizaron durante el tiempo especificado en la cláusula CONTENIDO EN. En la siguiente secuencia de comandos, el "Dónde La cláusula ” omitirá los productos que están presentes en el tblProduct mesa. Ejecutaremos la siguiente consulta:

declare @StartDateTime datetime
declare @EndDateTime datetime
set @StartDateTime=convert(datetime2,getdate()-1)
set @EndDateTime=convert(datetime2,getdate())

select ProductID, Name, ProductNumber,Product_Valid_From, Product_Valid_To from tblProduct For SYSTEM_TIME Contained IN ( @StartDateTime , @EndDateTime) Where Name not in (Select Name from tblProduct)

La consulta anterior ha omitido los registros que se han actualizado; por lo tanto, devolvió 13 filas. Vea la siguiente captura de pantalla:

Al utilizar el método anterior, podremos obtener la lista de productos que se han eliminado del tblProduct mesa.

Rellenar registros actualizados

Para completar los registros actualizados, debemos omitir los registros que se eliminaron durante el tiempo especificado en el Contenido-IN cláusula. En la siguiente secuencia de comandos, el "Dónde La cláusula ” incluirá los productos que están presentes en el tblProduct mesa. Ejecutaremos la siguiente consulta:

 declare @StartDateTime datetime
declare @EndDateTime datetime
set @StartDateTime=convert(datetime2,getdate()-1)
set @EndDateTime=convert(datetime2,getdate())
select ProductID, Name, ProductNumber,Product_Valid_From, Product_Valid_To from tblProduct For SYSTEM_TIME Contained IN ( @StartDateTime , @EndDateTime) Where Name in (Select Name from tblProduct)

La consulta anterior omitió los registros que se actualizaron, por lo que devolvió 9 filas. Vea la siguiente captura de pantalla:

Con el método anterior, podremos identificar los registros que se han actualizado con valores incorrectos y los registros que se han eliminado de la tabla temporal.

Resumen

En este artículo, he cubierto:

  1. Introducción de alto nivel de tablas temporales.
  2. Se explica cómo se actualizarán las columnas de período al ejecutar consultas DML.
  3. Una demostración para recuperar la lista de productos que se ha eliminado y actualizado con el precio incorrecto, de la tabla temporal. Este informe se puede utilizar con fines de auditoría.