sql >> Database teknologi >  >> RDS >> Database

Udførelse af dataændringsrevision ved hjælp af tidsmæssig tabel

SQL Server 2016 har introduceret en funktion kaldet 'Systemversioned temporal table'. Ved hjælp af normal tabel kan du hente aktuelle data; mens du bruger en systemversioneret tidstabel, kan du hente data, som er blevet slettet eller opdateret tidligere. For at gøre det, vil en tidsmæssig tabel oprette en historietabel. Historietabellen gemmer gamle data med "start_tid " og "sluttidspunkt ”. Hvilket angiver en tidsperiode, hvor rekorden var aktiv.

Eksempel:Hvis du opdaterer en produktpris fra 30 til 50 ved at forespørge i en normal tabel, kan du hente den opdaterede produktpris, som er 50. Ved hjælp af en tidsmæssig tabel kan du hente den gamle værdi, som er 30.

Ved at bruge tidsmæssige tabeller kan man udføre:

  1. Spor historie for en post :vi kan gennemgå en værdi af den specifikke post, som er blevet ændret over tid.
  2. Gendannelse på rekordniveau :hvis vi har slettet en specifik post fra tabellen, eller en post er beskadiget, kan vi hente den fra historietabellen.

Tidsmæssige tabeller fanger dato-klokkeslæt for en post baseret på de fysiske datoer (kalenderdato) for postens opdatering og sletning. I øjeblikket understøtter den ikke versionering baseret på de logiske datoer. For eksempel, hvis du opdaterer produktnavnet ved hjælp af UPDATE-sætningen kl. 13:00, vil tidstabellen bevare historikken for produktets navn indtil kl. 13:00. Derefter vil et nyt navn være gældende. Men hvad nu hvis produktnavneændringen var beregnet til at starte fra kl. 14.00? Det betyder, at du skal opdatere erklæringen perfekt til tiden for at få den til at fungere, og du burde have udført UPDATE-erklæringen kl. 14.00 i stedet for kl. 13.00.

Tidsmæssige tabeller har følgende forudsætninger:

  1. Der skal defineres en primær nøgle.
  2. To kolonner skal defineres for at registrere starttidspunkt og sluttidspunkt med datatypen datetime2. Disse kolonner kaldes SYSTEM_TIME-kolonner.

De har også nogle begrænsninger:

  1. I STEDET FOR triggere og OLTP i hukommelsen er ikke tilladt.
  2. Historiktabeller kan ikke have nogen begrænsning.
  3. Data i historietabellen kan ikke ændres.

Oprettelse af en systemversioneret tabel

Følgende script vil blive brugt til at oprette en simpel systemversioneret tabel:

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

I ovenstående script har jeg defineret HISTORY_TABLE ved navn dbo. Produkt_Ændringshistorik. Hvis du ikke angiver et navn til historiktabellen, vil SQL Server automatisk oprette en historiktabel med følgende struktur.

Dbo.MSSQL_TemporalHistoryFor_xxx, hvor xxx er objekt-id'et.

Den tidsmæssige tabel vil se ud, som den er vist på skærmbilledet nedenfor:

Hvordan opdateres periodekolonner, når der udføres DML-sætning på Temporal Table?

Hver gang vi udfører indsæt, opdaterer og sletter en forespørgsel på den tidsmæssige tabel, vil periodekolonnerne (SysStartDate og SysEndDate) blive opdateret.

Indsæt forespørgsel

Når vi udfører INSERT-operationen på den tidsmæssige tabel, sætter systemet værdien af ​​SysStartTime-kolonnen til starttidspunktet for den aktuelle transaktion og markerer rækken som åben.

Lad os indsætte nogle rækker i 'Produkter ’ tabel og gennemgå, hvordan data gemmes i denne tabel.

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

Som vist på ovenstående skærmbillede er værdien af ​​'Product_Valid_From kolonnen er "2018-04-02 06:55:04.4865670 ’ som er datoen for rækkeindsættelse. Og værdien af ​​'Product_Valid_To kolonnen er "9999-12-31 23:59:59.9999999 ’, hvilket indikerer, at rækken er åben.

Opdater forespørgsel

Når vi udfører en opdateringsforespørgsel på den tidsmæssige tabel, vil systemet gemme de foregående rækkeværdier i historiktabellen og indstille den aktuelle transaktionstid som Sluttid og opdatere den nuværende tabel med en ny værdi. SysStartTime vil være starttidspunktet for transaktionen og SysEndTime vil være det maksimale 9999-12-31.

Lad os ændre produktprisen for "Mus ' fra 500 til 250. Vi kontrollerer outputtet af 'Produkt ’.

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

select * from Prodcuts where Product_name='Mouse'

Som du kan se på ovenstående skærmbillede, er en værdi af "Product_Valid_From ' kolonne er blevet ændret. Den nye værdi er den aktuelle transaktionstid (UTC). Og værdien af ​​'Product_Valid_To kolonnen er '9999-12-31 23:59:59.9999999 ’, hvilket indikerer, at rækken er åben og har opdateret prisen.

Lad os se output fra Product_change_history tabel ved at forespørge på den.

select * from Product_Change_History where Product_name='Mouse'

Som du kan se på ovenstående skærmbillede, er der tilføjet en række i Product_change_history tabel, som har en gammel version af rækken. Værdien af ​​"Product_cost ' er 500, værdien af ​​'Product_valid_From ’ er tidspunktet, hvor posten blev indsat og værdien af ​​Product_Valid_To kolonne er, når værdien af ​​ kolonne Produktpris blev opdateret. Denne rækkeversion betragtes som lukket.

Slet forespørgsel

Når vi sletter en post fra den tidsmæssige tabel, vil systemet gemme den aktuelle version af rækken i historiktabellen og indstille den aktuelle transaktionstid som Sluttid og slette posten fra den aktuelle tabel.

Lad os slette registreringen af ​​'Headset'.

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

Lad os se resultatet af Product_change_history tabel ved at forespørge på den.

select * from Product_Change_History where Product_name='Headset'

Som du kan se på ovenstående skærmbillede, er der tilføjet en række i Product_change_history tabel, som blev slettet fra den aktuelle tabel. Værdien af ​​"Product_valid_From ’ er det tidspunkt, hvor posten blev indsat og værdien af ​​Product_Valid_To kolonne er det tidspunkt, hvor rækken blev slettet, hvilket indikerer, at rækkeversionen er lukket.

Revisionsdataændringer for et bestemt tidspunkt

For at revidere dataændringerne for en specifik tabel bør vi udføre den tidsbaserede analyse af tidsmæssige tabeller. For at gøre det skal vi bruge 'FOR SYSTEM_TIME ’ klausul med nedenstående tidsspecifikke underklausuler til forespørgselsdataene på tværs af de aktuelle tabeller og historietabeller. Lad mig forklare outputtet af forespørgsler ved hjælp af forskellige underklausuler. Nedenfor er opsætningen:

  1. Jeg indsatte et produkt med navnet 'Flat Washer 8' med listepris 0,00 i tidstabellen kl. 09:02:25.
  2. Jeg ændrede listeprisen kl. 10:13:56. Nypris er 500,00.

SOM FRA

Denne klausul vil blive brugt til at hente status for posterne for et givet tidspunkt iSOM OF underparagraf. For at forstå det, lad os udføre flere forespørgsler:

Først vil vi udføre en forespørgsel ved hjælp af SOM FRA klausul med "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'

Som du kan se på ovenstående skærmbillede, returnerede forespørgslen én række med den opdaterede værdi af "ListPrice ” og værdien af ​​Product_Valid_To er maksimum for dato.

Lad os udføre en anden forespørgsel ved at bruge SOM OF c lause med "SystemTime =09:10:56: ”.

Nu, som du kan se i ovenstående skærmbillede, er værdien af ​​"ListPrice ” er 0,00.

Fra Til

Denne klausul returnerer de aktive rækker mellem og . For at forstå det, lad udføre følgende forespørgsel ved hjælp af Fra..Til underklausul med "SystemTime Fra '2018-04-20 09:02:25' til '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'

Følgende skærmbillede viser forespørgselsresultatet:

MELLEM Og

Denne klausul ligner FROM.. Til klausul. Den eneste forskel er, at den vil inkludere de poster, der var aktive på . For at forstå det, lad os udføre følgende forespørgsel:

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'

Følgende skærmbillede viser forespørgselsresultatet:

Inkluderet I (, )

Denne underklausul vil inkludere de poster, der blev aktive og sluttede inden for det angivne datointerval. Det inkluderer ikke de aktive poster. For at forstå det, udfør nedenstående forespørgsel ved at bruge "Indeholdt IN '2018-04-20 09:02:25 ' til '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'

Følgende skærmbillede viser forespørgselsresultatet:

Scenarie

En organisation bruger en opgørelsessoftware. Denne inventarsoftware bruger en produkttabel, som er en systemversions tidstabel. På grund af en applikationsfejl blev få produkter slettet, og priserne på produkterne blev også forkert opdateret.

Som DBA'er skal vi undersøge dette problem og gendanne de data, der er forkert opdateret og slettet fra tabellen.

For at simulere ovenstående scenarie, lad os oprette en tabel med nogle meningsfulde data. Jeg vil oprette en ny tidslig tabel med navnet 'tblProduct ' på demodatabasen, som er en klon af [Produktion].[Produkter] tabel i AdventureWorks2014-databasen.

For at udføre ovenstående opgave har jeg fulgt nedenstående trin:

  1. Udtrukket "opret tabelscript" [Produktion]. [Produkter] fra AdventureWorks2014-databasen.
  2. Fjernede alle "begrænsninger og indekser" fra scriptet.
  3. Holdte kolonnestrukturen uændret.
  4. For at konvertere den til en tidsmæssig tabel tilføjede jeg SysStartTime- og SysEndTime-kolonner.
  5. Aktiveret System_Versioning.
  6. Specificeret historiktabel.
  7. Udførte scriptet på edemo-databasen.

Nedenfor er scriptet:

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

Jeg har importeret data fra produkttabellen i "AdventureWorks2014"-databasen til produkttabellen for "DemoDatabase" ved at udføre følgende 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

Jeg slettede produktnavneposterne, der starter med 'Thin-Jam Hex Nut' fra tblProduct. Jeg har også ændret prisen på de produkter, hvor navnene starter med Flat Washer på 'tblProduct ’ tabel ved at udføre følgende forespørgsel:

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

Vi er opmærksomme på tidspunktet, hvor data blev slettet. Derfor vil vi bruge Contained-IN underklausulen for at identificere, hvilke data der er blevet slettet. Som jeg nævnte ovenfor, vil det give mig listen over poster, der har rækkeversioner, som blev aktive og sluttede inden for det angivne datointerval. Derefter udføres nedenstående forespørgsel:

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)

Ved at udføre ovenstående forespørgsel er 22 rækker blevet hentet.

Indeholdt-IN klausul vil udfylde de rækker, der blev opdateret og slettet i løbet af det givne tidspunkt.

Udfyld slettede poster:

For at udfylde de slettede poster, skal vi springe de poster over, som blev opdateret i løbet af den tid, der er angivet i Contained-IN-klausulen. I nedenstående script, "Hvor ”-klausulen vil springe de produkter over, der er til stede i tblProduct bord. Vi udfører følgende forespørgsel:

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)

Ovenstående forespørgsel har sprunget over de poster, der er blevet opdateret; derfor returnerede den 13 rækker. Se nedenstående skærmbillede:

Ved at bruge ovenstående metode vil vi være i stand til at få listen over produkter, der er blevet slettet fra tblProduct tabel.

Udfyld opdaterede poster

For at udfylde de opdaterede poster skal vi springe de poster over, der blev slettet i løbet af den tid, der er angivet iIndeholdt-IN klausul. I nedenstående script, "Hvor ”-klausulen vil inkludere de produkter, der er til stede i tblProduct bord. Vi udfører følgende forespørgsel:

 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)

Ovenstående forespørgsel har sprunget over de poster, der er blevet opdateret, og returnerede derfor 9 rækker. Se nedenstående skærmbillede:

Ved at bruge ovenstående metode vil vi være i stand til at identificere de poster, der er blevet opdateret med forkerte værdier, og de poster, der er blevet slettet fra den tidsmæssige tabel.

Oversigt

I denne artikel har jeg dækket:

  1. Introduktion på højt niveau af tidsmæssige tabeller.
  2. Forklaret, hvordan periodekolonner vil blive opdateret ved at udføre DML-forespørgsler.
  3. En demo for at hente listen over produkter, der er blevet slettet og opdateret med den forkerte pris, fra den tidsmæssige tabel. Denne rapport kan bruges til revisionsformål.

  1. PostgreSQL listevisninger

  2. 7 måder at finde dublerede rækker, mens du ignorerer den primære nøgle i MySQL

  3. Konvertering af SELECT DISTINCT ON-forespørgsler fra Postgresql til MySQL

  4. Lagret procedure - returner identitet som outputparameter eller skalar