sql >> Database >  >> RDS >> Database

Audit van gegevenswijzigingen uitvoeren met behulp van een tijdelijke tabel

SQL Server 2016 heeft een functie geïntroduceerd met de naam 'Systeemversie van tijdelijke tabel'. Met behulp van de normale tabel kunt u actuele gegevens ophalen; terwijl u een tijdelijke tabel met systeemversie gebruikt, kunt u gegevens ophalen die in het verleden zijn verwijderd of bijgewerkt. Om dat te doen, maakt een tijdelijke tabel een geschiedenistabel. De geschiedenistabel zal oude gegevens opslaan met "start_time ” en “eindtijd ”. Dit geeft een tijdsperiode aan waarvoor het record actief was.

Voorbeeld:als u een productprijs bijwerkt van 30 naar 50 door een normale tabel op te vragen, kunt u de bijgewerkte productprijs van 50 ophalen. Met behulp van een tijdelijke tabel kunt u de oude waarde van 30 ophalen.

Met behulp van tijdelijke tabellen kan men het volgende doen:

  1. Houd de geschiedenis van een record bij :we kunnen een waarde van het specifieke record bekijken, die in de loop van de tijd is gewijzigd.
  2. Herstel op recordniveau :als we een specifiek record uit de tabel hebben verwijderd of als een record beschadigd is, kunnen we het ophalen uit de geschiedenistabel.

Tijdelijke tabellen leggen de datum-tijd van een record vast op basis van de fysieke datums (Kalenderdatum) van het bijwerken en verwijderen van het record. Momenteel ondersteunt het geen versiebeheer op basis van de logische datums. Als u bijvoorbeeld de productnaam bijwerkt met de UPDATE-instructie om 13:00 uur, houdt de tijdelijke tabel de geschiedenis van de naam van het product bij tot 13:00 uur. Daarna zal een nieuwe naam van toepassing zijn. Maar wat als de wijziging van de productnaam vanaf 14:00 uur zou beginnen? Dit betekent dat je het statement op tijd perfect moet updaten om het te laten werken en dat je het UPDATE statement om 14:00 uur in plaats van 13:00 uur had moeten uitvoeren.

Tijdelijke tabellen hebben de volgende vereisten:

  1. Er moet een primaire sleutel worden gedefinieerd.
  2. Er moeten twee kolommen worden gedefinieerd om de begin- en eindtijd vast te leggen met het datatype datetime2. Deze kolommen worden SYSTEM_TIME kolommen genoemd.

Ze hebben ook enkele beperkingen:

  1. IN PLAATS VAN triggers en In-Memory OLTP zijn niet toegestaan.
  2. Geschiedenistabellen kunnen geen enkele beperking hebben.
  3. Gegevens in geschiedenistabel kunnen niet worden gewijzigd.

Een tabel met systeemversie maken

Het volgende script wordt gebruikt om een ​​eenvoudige systeemversietabel te maken:

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));

In het bovenstaande script heb ik HISTORY_TABLE gedefinieerd met de naam dbo. Product_Wijziging_Geschiedenis. Als u geen naam opgeeft voor de geschiedenistabel, maakt SQL Server automatisch een geschiedenistabel met de volgende structuur.

Dbo.MSSQL_TemporalHistoryFor_xxx, waarbij xxx de object-ID is.

De temporele tabel ziet eruit zoals deze wordt weergegeven in de onderstaande schermafbeelding:

Hoe worden periodekolommen bijgewerkt bij het uitvoeren van een DML-instructie op de tijdelijke tabel?

Telkens wanneer we een query uitvoeren op de tijdelijke tabel invoegen, bijwerken en verwijderen, worden de periodekolommen (SysStartDate en SysEndDate) bijgewerkt.

Query invoegen

Wanneer we de INSERT-bewerking op de tijdelijke tabel uitvoeren, stelt het systeem de waarde van de SysStartTime-kolom in op de starttijd van de huidige transactie en markeert het de rij als open.

Laten we enkele rijen invoegen in de 'Producten ’ tabel en bekijk hoe gegevens in deze tabel worden opgeslagen.

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

Zoals te zien is in de bovenstaande schermafbeelding, is de waarde van de 'Product_Valid_From ’ kolom is ‘2018-04-02 06:55:04.4865670 ' wat de rij-invoegdatum is. En de waarde van de 'Product_Valid_To ’ kolom is ‘9999-12-31 23:59:59.999999 ’, wat aangeeft dat de rij open is.

Zoekopdracht bijwerken

Wanneer we een update-query uitvoeren op de tijdelijke tabel, slaat het systeem de vorige rijwaarden op in de geschiedenistabel en stelt het de huidige transactietijd in als EndTime en werk de huidige tabel bij met een nieuwe waarde. SysStartTime is de starttijd van de transactie en SysEndTime zal het maximum zijn van 9999-12-31.

Laten we de productkosten van 'Muis . wijzigen ’ van 500 tot 250. We controleren de uitvoer van ‘Product ’.

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

select * from Prodcuts where Product_name='Mouse'

Zoals je kunt zien in de bovenstaande schermafbeelding, is een waarde van de 'Product_Valid_From ’ kolom is gewijzigd. De nieuwe waarde is de huidige transactietijd (UTC). En de waarde van de 'Product_Valid_To ’ kolom is ‘9999-12-31 23:59:59,999999 ’, wat aangeeft dat de rij open is en de prijs heeft bijgewerkt.

Laten we eens kijken naar de uitvoer van de Product_change_history tabel door ernaar te vragen.

select * from Product_Change_History where Product_name='Mouse'

Zoals je kunt zien in de bovenstaande schermafbeelding, is er een rij toegevoegd in Product_change_history tabel, die een oude versie van de rij heeft. Waarde van 'Productkosten ’ is 500, waarde van ‘Product_valid_From ' is het tijdstip waarop de record werd ingevoegd en de waarde van Product_Valid_To kolom is wanneer de waarde van kolom Product_kosten is geupdated. Deze rijversie wordt als gesloten beschouwd.

Query verwijderen

Wanneer we een record uit de tijdelijke tabel verwijderen, slaat het systeem de huidige versie van de rij op in de geschiedenistabel en stelt het de huidige transactietijd in als EndTime en verwijdert het record uit de huidige tabel.

Laten we de record van 'Headset' verwijderen.

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

Laten we eens kijken naar de uitvoer van Product_change_history tabel door ernaar te vragen.

select * from Product_Change_History where Product_name='Headset'

Zoals je kunt zien in de bovenstaande schermafbeelding, is er een rij toegevoegd in Product_change_history tabel, die uit de huidige tabel is verwijderd. Waarde van 'Product_valid_From ' is het tijdstip waarop de record werd ingevoegd en de waarde van de Product_Valid_To kolom is het tijdstip waarop de rij werd verwijderd, wat aangeeft dat de rijversie is gesloten.

Gegevenswijzigingen voor een bepaalde tijd controleren

Om de gegevenswijzigingen voor een specifieke tabel te controleren, moeten we de tijdgebaseerde analyse van tijdelijke tabellen uitvoeren. Om dat te doen, moeten we de 'FOR SYSTEM_TIME . gebruiken ' clausule met hieronder tijdspecifieke sub-clausules voor de querygegevens in de huidige en geschiedenistabellen. Laat me de uitvoer van query's uitleggen met behulp van verschillende subclausules. Hieronder is de opstelling:

  1. Ik heb om 09:02:25 uur een product met de naam 'Flat Washer 8' met catalogusprijs 0,00 in de tijdelijke tabel geplaatst.
  2. Ik heb de catalogusprijs gewijzigd om 10:13:56 uur. Nieuwprijs is 500,00.

VAN

Deze clausule wordt gebruikt om de status van de records op te halen voor een bepaalde tijd in de AS OF bijzin. Laten we een aantal zoekopdrachten uitvoeren om het te begrijpen:

Eerst zullen we een zoekopdracht uitvoeren met de AS OF clausule met ”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'

Zoals u kunt zien in de bovenstaande schermafbeelding, heeft de query één rij geretourneerd met de bijgewerkte waarde "ListPrice ” en waarde van Product_Valid_To is het maximum van de datum.

Laten we nog een query uitvoeren met de AS OF c lause met "SystemTime =09:10:56: ”.

Zoals je kunt zien in de bovenstaande schermafbeelding, is de waarde van "ListPrice ” is 0,00.

Van tot

Deze clausule retourneert de rijen die actief zijn tussen en . Om het te begrijpen, laat u de volgende query uitvoeren met de Van..To subclausule met "SystemTime Van '2018-04-20 09:02:25' tot '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'

De volgende schermafbeelding toont het zoekresultaat:

TUSSEN en

Deze clausule is vergelijkbaar met de FROM.. Naar clausule. Het enige verschil is dat het de records bevat die actief waren op . Laten we de volgende query uitvoeren om het te begrijpen:

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'

De volgende schermafbeelding toont het zoekresultaat:

Opgenomen IN (, )

Deze subclausule omvat de records die actief werden en eindigden binnen het gespecificeerde datumbereik. Het omvat niet de actieve records. Om het te begrijpen, voert u onderstaande query uit met "Bevat IN '2018-04-20 09:02:25 ' naar '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'

De volgende schermafbeelding toont het zoekresultaat:

Scenario

Een organisatie gebruikt een inventarisatiesoftware. Die inventarissoftware gebruikt een producttabel die een tijdelijke tabel met systeemversies is. Door een applicatiefout zijn er weinig producten verwijderd en zijn de prijzen van de producten ook verkeerd bijgewerkt.

Als DBA's moeten we dit probleem onderzoeken en de gegevens herstellen die ten onrechte zijn bijgewerkt en uit de tabel zijn verwijderd.

Laten we, om het bovenstaande scenario te simuleren, een tabel maken met enkele zinvolle gegevens. Ik ga een nieuwe tijdelijke tabel maken met de naam 'tblProduct ' in de Demo-database, een kloon van de [Production].[Products] tabel van de AdventureWorks2014-database.

Om bovenstaande taak uit te voeren, heb ik onderstaande stappen gevolgd:

  1. Geëxtraheerd "tabelscript maken" [Productie]. [Producten] uit de AdventureWorks2014-database.
  2. Alle "beperkingen en indexen" uit het script verwijderd.
  3. De kolomstructuur ongewijzigd gehouden.
  4. Om het naar een tijdelijke tabel te converteren, heb ik kolommen SysStartTime en SysEndTime toegevoegd.
  5. System_Versioning ingeschakeld.
  6. Gespecificeerde geschiedenistabel.
  7. Het script uitgevoerd in de edemo-database.

Hieronder staat het script:

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

Ik heb gegevens geïmporteerd uit de producttabel van de "AdventureWorks2014"-database naar de producttabel van "DemoDatabase" door het volgende script uit te voeren:

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

Ik heb de productnaamrecords die beginnen met 'Thin-Jam Hex Nut' van tblProduct verwijderd. Ik heb ook de prijs gewijzigd van de producten waarvan de namen beginnen met Flat Washer op 'tblProduct ’ tabel door de volgende query uit te voeren:

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%'

We zijn op de hoogte van het tijdstip waarop gegevens zijn verwijderd. Om vast te stellen welke gegevens zijn verwijderd, gebruiken we de subclausule Contained-IN. Zoals ik hierboven al zei, krijg ik de lijst met records met rijversies die actief werden en eindigden binnen het opgegeven datumbereik. Vervolgens onderstaande query uitgevoerd:

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)

Door de bovenstaande query uit te voeren, zijn 22 rijen opgehaald.

DeContained-IN clausule zal de rijen vullen die gedurende de gegeven tijd zijn bijgewerkt en verwijderd.

Verwijderde records invullen:

Om de verwijderde records te vullen, moeten we de records overslaan die zijn bijgewerkt gedurende de tijd die is gespecificeerd in de Contained-IN-clausule. In het onderstaande script wordt de "Waar ”-clausule slaat de producten over die aanwezig zijn in het tblProduct tafel. We zullen de volgende query uitvoeren:

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)

De bovenstaande query heeft de records overgeslagen die zijn bijgewerkt; vandaar dat het 13 rijen heeft geretourneerd. Zie onderstaand screenshot:

Door de bovenstaande methode te gebruiken, kunnen we de lijst met producten ophalen die zijn verwijderd uit het tblProduct tafel.

Geüpdatete records invullen

Om de bijgewerkte records te vullen, moeten we de records overslaan die zijn verwijderd gedurende de tijd die is opgegeven in de Contained-IN clausule. In het onderstaande script wordt de "Waar ”-clausule omvat de producten die aanwezig zijn in het tblProduct tafel. We zullen de volgende query uitvoeren:

 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)

De bovenstaande query heeft de records overgeslagen die zijn bijgewerkt en heeft daarom 9 rijen geretourneerd. Zie onderstaand screenshot:

Met behulp van bovenstaande methode kunnen we de records identificeren die zijn bijgewerkt met verkeerde waarden en de records die zijn verwijderd uit de tijdelijke tabel.

Samenvatting

In dit artikel heb ik het volgende behandeld:

  1. Introductie op hoog niveau van temporele tabellen.
  2. Uitgelegd hoe periodekolommen worden bijgewerkt door DML-query's uit te voeren.
  3. Een demo om de lijst met producten op te halen die zijn verwijderd en bijgewerkt met de verkeerde prijs, uit de tijdelijke tabel. Dit rapport kan worden gebruikt voor controledoeleinden.

  1. Een variabele periode in een interval gebruiken in Postgres

  2. Hoe root-wachtwoord opnieuw in te stellen in MySQL 8.0

  3. Converteer String ISO-8601-datum naar het tijdstempelgegevenstype van orakel

  4. Een lijst met geldige tijdzones retourneren in Oracle Database