sql >> Databasteknik >  >> RDS >> Database

Utföra revision av dataändringar med hjälp av tidstabell

SQL Server 2016 har introducerat en funktion som heter "Systemversioned temporal table". Med normal tabell kan du hämta aktuella data; medan du använder en tidstabell med systemversion kan du hämta data som har raderats eller uppdaterats tidigare. För att göra det kommer en tidstabell att skapa en historiktabell. Historiktabellen lagrar gamla data med "start_tid ” och ”sluttid ”. Vilket indikerar en tidsperiod under vilken posten var aktiv.

Exempel:Om du uppdaterar ett produktpris från 30 till 50 genom att fråga en normal tabell kan du hämta det uppdaterade produktpriset som är 50. Med hjälp av en tidstabell kan du hämta det gamla värdet som är 30.

Genom att använda tidstabeller kan man utföra:

  1. Spåra historik för en post :vi kan granska ett värde för den specifika posten, som har ändrats under tiden.
  2. Återställning på rekordnivå :om vi raderade en specifik post från tabellen eller om en post är skadad, kan vi hämta den från historiktabellen.

Temporala tabeller fångar datum-tid för en post baserat på de fysiska datumen (kalenderdatum) för postens uppdatering och radering. För närvarande stöder den inte versionshantering baserat på logiska datum. Om du till exempel uppdaterar produktnamnet med hjälp av UPDATE-satsen kl. 13.00, kommer tidstabellen att behålla historiken för produktens namn fram till kl. 13.00. Därefter kommer ett nytt namn att gälla. Men vad händer om produktnamnsändringen var tänkt att börja från 14:00? Det betyder att du måste uppdatera uttalandet i tid för att det ska fungera och att du borde ha kört UPDATE-satsen klockan 14.00 istället för 13.00.

Temporala tabeller har följande förutsättningar:

  1. En primärnyckel måste definieras.
  2. Två kolumner måste definieras för att registrera starttiden och sluttid med datatypen datetime2. Dessa kolumner kallas SYSTEM_TIME-kolumner.

De har också vissa begränsningar:

  1. I STÄLLET FÖR utlösare och OLTP i minnet är inte tillåtna.
  2. Historiktabeller kan inte ha några begränsningar.
  3. Data i historiktabellen kan inte ändras.

Skapa en systemversionstabell

Följande skript kommer att användas för att skapa en enkel systemversionstabell:

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 skriptet ovan har jag definierat HISTORY_TABLE som heter dbo. Produkt_ändringshistorik. Om du inte anger ett namn för historiktabellen kommer SQL Server automatiskt att skapa en historiktabell med följande struktur.

Dbo.MSSQL_TemporalHistoryFor_xxx, där xxx är objektets ID.

Den tidsmässiga tabellen kommer att se ut som den visas i skärmdumpen nedan:

Hur kommer periodkolumner att uppdateras när DML-satsen körs på Temporal Table?

Varje gång vi utför infoga, uppdatera och radera en fråga i den temporala tabellen kommer periodkolumnerna (SysStartDate och SysEndDate) att uppdateras.

Infoga fråga

När vi utför INSERT-operationen på den temporala tabellen, ställer systemet in värdet på kolumnen SysStartTime till starttiden för den aktuella transaktionen och markerar raden som öppen.

Låt oss infoga några rader i "Produkter ’ tabell och granska hur data lagras i den här tabellen.

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 visas i skärmdumpen ovan, värdet för "Product_Valid_From kolumnen är "2018-04-02 06:55:04.4865670 ’ vilket är datumet för radinsättning. Och värdet på "Product_Valid_To kolumnen är "9999-12-31 23:59:59.9999999 ’, vilket indikerar att raden är öppen.

Uppdatera fråga

När vi kör en uppdateringsfråga på den temporala tabellen kommer systemet att lagra de föregående radvärdena i historiktabellen och ställa in den aktuella transaktionstiden som Sluttid och uppdatera den aktuella tabellen med ett nytt värde. SysStartTime kommer att vara starttiden för transaktionen och SysEndTime kommer att vara maximalt 9999-12-31.

Låt oss ändra produktkostnaden för "mus ' från 500 till 250. Vi kommer att kontrollera resultatet av '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 i skärmdumpen ovan är värdet "Product_Valid_From kolumnen har ändrats. Det nya värdet är den aktuella transaktionstiden (UTC). Och värdet på "Product_Valid_To kolumnen är '9999-12-31 23:59:59.9999999 ’, vilket indikerar att raden är öppen och har uppdaterat priset.

Låt oss titta på resultatet av Product_change_history tabell genom att fråga den.

select * from Product_Change_History where Product_name='Mouse'

Som du kan se i skärmdumpen ovan har en rad lagts till i Product_change_history tabell, som har en gammal version av raden. Värdet av "Product_cost ' är 500, värdet på 'Product_valid_From ’ är tiden då posten infogades och värdet på Product_Valid_To kolumnen är när värdet för kolumnen Produktkostnad uppdaterades. Denna radversion anses vara stängd.

Ta bort sökfråga

När vi tar bort en post från den temporala tabellen kommer systemet att lagra den aktuella versionen av raden i historiktabellen och ställa in den aktuella transaktionstiden som Sluttid och radera posten från den aktuella tabellen.

Låt oss ta bort posten för "Headset".

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

Låt oss titta på resultatet av Product_change_history tabell genom att fråga den.

select * from Product_Change_History where Product_name='Headset'

Som du kan se i skärmdumpen ovan har en rad lagts till i Product_change_history tabell, som togs bort från den aktuella tabellen. Värdet för "Product_valid_From ’ är den tidpunkt då posten infogades och värdet för Product_Valid_To kolumnen är den tidpunkt då raden raderades vilket indikerar att radversionen är stängd.

Revisionsdata ändras för en viss tid

För att granska dataändringarna för en specifik tabell bör vi utföra den tidsbaserade analysen av tidstabeller. För att göra det måste vi använda "FOR SYSTEM_TIME ’ klausul med nedanstående tidsspecifika underklausuler till frågedata över nuvarande tabeller och historiktabeller. Låt mig förklara resultatet av frågor med hjälp av olika undersatser. Nedan är inställningen:

  1. Jag infogade en produkt med namnet 'Flat Washer 8' med listpris 0,00 i tidstabellen kl. 09:02:25.
  2. Jag ändrade listpriset kl. 10:13:56. Nypris är 500,00.

FRÅN

Denna klausul kommer att användas för att hämta status för posterna för en given tid iFRÅN underklausul. För att förstå det, låt oss köra flera frågor:

Först kommer vi att köra en fråga med hjälp av FRÅN 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 i skärmdumpen ovan returnerade frågan en rad med det uppdaterade värdet "ListPrice ” och värdet på Product_Valid_To är maxvärdet för datum.

Låt oss köra en annan fråga med FRÅN c lause med "SystemTime =09:10:56: ”.

Nu, som du kan se i skärmdumpen ovan, är värdet på "ListPrice ” är 0,00.

Från Till

Denna sats returnerar raderna som är aktiva mellan och . För att förstå det, låt köra följande fråga med Från..Till undersats med "SystemTime Från '2018-04-20 09:02:25' till '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öljande skärmdump visar frågeresultatet:

MELLAN Och

Den här klausulen liknar FRÅN... Till klausul. Den enda skillnaden är att den kommer att inkludera de poster som var aktiva vid . För att förstå det, låt oss köra följande fråga:

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öljande skärmdump visar frågeresultatet:

Ingår I (, )

Denna underklausul kommer att inkludera de poster som blev aktiva och slutade inom det angivna datumintervallet. Den inkluderar inte de aktiva posterna. För att förstå det, kör nedanstående fråga med "Contained IN '2018-04-20 09:02:25 ' till '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öljande skärmdump visar frågeresultatet:

Scenario

En organisation använder en inventeringsprogramvara. Den inventeringsprogramvaran använder en produkttabell som är en tidstabell för systemversionen. På grund av ett programfel togs få produkter bort och priserna på produkterna uppdaterades också felaktigt.

Som DBA:er måste vi undersöka det här problemet och återställa data som felaktigt uppdaterades och raderades från tabellen.

För att simulera scenariot ovan, låt oss skapa en tabell med lite meningsfull data. Jag kommer att skapa en ny temporal tabell med namnet 'tblProduct ’ på demodatabasen som är en klon av [Produktion].[Produkter] tabellen i AdventureWorks2014-databasen.

För att utföra ovanstående uppgift har jag följt stegen nedan:

  1. Extraherat "skapa tabellskript" [Produktion]. [Produkter] från databasen AdventureWorks2014.
  2. Ta bort alla "begränsningar och index" från skriptet.
  3. Hållte kolumnstrukturen oförändrad.
  4. För att konvertera den till en tidstabell lade jag till kolumnerna SysStartTime och SysEndTime.
  5. Aktiverad System_Versioning.
  6. Specificerad historiktabell.
  7. Körde skriptet på edemo-databasen.

Nedan är skriptet:

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

Jag har importerat data från produkttabellen i "AdventureWorks2014"-databasen till produkttabellen för "DemoDatabase" genom att köra följande skript:

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

Jag tog bort produktnamnsposterna som börjar med 'Thin-Jam Hex Nut' från tblProduct. Jag ändrade också priset på produkterna där namnen börjar med Flat Washer på 'tblProduct ’ tabell genom att köra följande fråga:

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 är medvetna om den tidpunkt då data raderades. Därför kommer vi att använda Contained-IN underklausul för att identifiera vilka data som har raderats. Som jag nämnde ovan kommer det att ge mig listan över poster som har radversioner som blev aktiva och slutade inom det angivna datumintervallet. Körde sedan nedanstående fråga:

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)

Genom att köra ovanstående fråga har 22 rader hämtats.

TheContained-IN kommer att fylla i raderna som uppdaterades och raderades under den givna tiden.

Fylla i raderade poster:

För att fylla i de raderade posterna måste vi hoppa över de poster som uppdaterades under den tid som anges i Contained-IN-klausulen. I skriptet nedan visas "Var ”-klausulen hoppar över produkterna som finns i tblProduct tabell. Vi kommer att utföra följande fråga:

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)

Ovanstående fråga har hoppat över posterna som har uppdaterats; därför gav den 13 rader. Se skärmbilden nedan:

Genom att använda metoden ovan kommer vi att kunna få listan över produkter som har tagits bort från tblProduct bord.

Fyll i uppdaterade poster

För att fylla i de uppdaterade posterna måste vi hoppa över de poster som raderades under den tid som anges iContained-IN klausul. I skriptet nedan visas "Var ”-klausulen kommer att inkludera produkterna som finns i tblProduct tabell. Vi kommer att utföra följande fråga:

 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)

Ovanstående fråga har hoppat över posterna som har uppdaterats och därför returnerade den 9 rader. Se skärmbilden nedan:

Genom att använda ovanstående metod kommer vi att kunna identifiera de poster som har uppdaterats med felaktiga värden och de poster som har tagits bort från den tidsmässiga tabellen.

Sammanfattning

I den här artikeln har jag täckt:

  1. Högnivåintroduktion av tidstabeller.
  2. Förklarat hur periodkolumner kommer att uppdateras genom att köra DML-frågor.
  3. En demo för att hämta listan över produkter som har raderats och uppdaterats med fel pris, från tidstabellen. Denna rapport kan användas för revisionsändamål.

  1. Hur får man tabelldefinition i Oracle?

  2. Installera MySQL Workbench för databasadministration

  3. Minimal loggning med INSERT...VÄLJ i tomma klustrade tabeller

  4. Är det möjligt att döda en enda fråga i Oracle utan att döda sessionen?