SQL Server 2016では、「システムバージョン管理テーブル」と呼ばれる機能が導入されました。通常のテーブルを使用して、現在のデータを取得できます。システムバージョンのテンポラルテーブルを使用しているときに、過去に削除または更新されたデータを取得できます。そのために、一時テーブルは履歴テーブルを作成します。履歴テーブルには、「 start_time」を含む古いデータが保存されます 」と「end_time 」。これは、レコードがアクティブだった期間を示します。
例:通常のテーブルをクエリして商品価格を30から50に更新すると、更新された商品価格50を取得できます。時間テーブルを使用すると、古い値30を取得できます。
テンポラルテーブルを使用して、次のことを実行できます。
- レコードの履歴を追跡する :時間の経過とともに変更された特定のレコードの値を確認できます。
- レコードレベルの回復 :テーブルから特定のレコードを削除した場合、またはレコードが破損している場合は、履歴テーブルからそのレコードを取得できます。
テンポラルテーブルは、レコードの更新と削除の物理的な日付(カレンダーの日付)に基づいて、レコードの日時をキャプチャします。現在、論理日付に基づくバージョン管理はサポートされていません。たとえば、午後1時にUPDATEステートメントを使用して製品名を更新すると、一時テーブルは午後1時まで製品名の履歴を保持します。その後、新しい名前が適用されます。ただし、製品名の変更が午後2時から開始することを意図していた場合はどうなりますか?つまり、ステートメントを機能させるには、ステートメントを時間どおりに完全に更新する必要があり、午後1時ではなく午後2時にUPDATEステートメントを実行する必要があります。
テンポラルテーブルには、次の前提条件があります。
- 主キーを定義する必要があります。
- datetime2データ型で開始時刻と終了時刻を記録するには、2つの列を定義する必要があります。これらの列はSYSTEM_TIME列と呼ばれます。
また、いくつかの制限があります:
- INSTEADOFトリガーとインメモリOLTPは許可されていません。
- 履歴テーブルに制約を設定することはできません。
- 履歴テーブルのデータは変更できません。
システムバージョンのテーブルの作成
次のスクリプトを使用して、単純なシステムバージョンのテーブルを作成します。
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));
上記のスクリプトでは、dboという名前のHISTORY_TABLEを定義しました。 Product_Change_History。履歴テーブルの名前を指定しない場合、SQLServerは次の構造の履歴テーブルを自動的に作成します。
Dbo.MSSQL_TemporalHistoryFor_xxx、ここでxxxはオブジェクトIDです。
下のスクリーンショットに示すように、時間テーブルは次のようになります。
テンポラルテーブルでDMLステートメントを実行すると、ピリオド列はどのように更新されますか?
テンポラルテーブルでクエリの挿入、更新、削除を実行するたびに、期間列(SysStartDateとSysEndDate)が更新されます。
クエリを挿入
テンポラルテーブルでINSERT操作を実行すると、システムはSysStartTime列の値を現在のトランザクションの開始時刻に設定し、行をオープンとしてマークします。
「製品」にいくつかの行を挿入しましょう ’テーブルを作成し、このテーブルにデータがどのように保存されているかを確認します。
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
上のスクリーンショットに示されているように、「 Product_Valid_From」の値は 」列は「2018-04-0206:55:04.4865670 ’は行挿入日です。そして、「 Product_Valid_To」の値 ’列は‘ 9999-12-31 23:59:59.9999999 ’、これは行が開いていることを示します。
クエリの更新
テンポラルテーブルで更新クエリを実行すると、システムは前の行の値を履歴テーブルに保存し、現在のトランザクション時間を EndTimeとして設定します。 現在のテーブルを新しい値で更新します。 SysStartTime トランザクションの開始時刻とSysEndTime 最大は9999-12-31になります。
「マウス」の商品コストを変更しましょう 」を500から250に変更します。「製品」の出力を確認します ’。
Begin tran UpdatePrice Update Prodcuts set Product_cost=200 where Product_name='Mouse' Commit tran UpdatePrice select * from Prodcuts where Product_name='Mouse'
上のスクリーンショットでわかるように、「 Product_Valid_From」の値 ’列が変更されました。新しい値は現在のトランザクション時間(UTC)です。そして、「 Product_Valid_To」の値 ’列は ‘9999-12-31 23:59:59.9999999 ’は、行が開いていて価格が更新されたことを示します。
Product_change_historyの出力を見てみましょう クエリを実行してテーブルを作成します。
select * from Product_Change_History where Product_name='Mouse'
上のスクリーンショットでわかるように、 Product_change_historyに行が追加されています 古いバージョンの行があるテーブル。 「Product_cost」の値 ’は500、‘ Product_valid_Fromの値 ’は、レコードが挿入された時刻と Product_Valid_Toの値です。 列は、Product_cost列の値が 更新されました。この行バージョンは閉じていると見なされます。
クエリの削除
テンポラルテーブルからレコードを削除すると、システムは行の現在のバージョンを履歴テーブルに保存し、現在のトランザクション時間をEndTimeとして設定し、現在のテーブルからレコードを削除します。
「ヘッドセット」のレコードを削除しましょう。
Begin tran DeletePrice delete from Prodcuts where product_name='Headset' Commit tran DeletePrice
Product_change_historyの出力を見てみましょう クエリを実行してテーブルを作成します。
select * from Product_Change_History where Product_name='Headset'
上のスクリーンショットでわかるように、 Product_change_historyに行が追加されています 現在のテーブルから削除されたテーブル。 ‘ Product_valid_Fromの値 ’は、レコードが挿入された時刻と Product_Valid_Toの値です。 columnは、行が削除された時刻であり、行バージョンが閉じられていることを示します。
特定の時間のデータ変更の監査
特定のテーブルのデータ変更を監査するには、時間テーブルの時間ベースの分析を実行する必要があります。そのためには、「 FOR SYSTEM_TIME」を使用する必要があります ’句は、現在のテーブルと履歴テーブル全体のクエリデータに対する時間固有のサブ句の下にあります。さまざまな副節を使用したクエリの出力について説明します。以下はセットアップです:
- 午前9時2分25秒に、定価0.00の「平ワッシャー8」という名前の製品を時間表に挿入しました。
- 定価を午前10時13分56秒に変更しました。新しい価格は500.00です。
AS OF
この句は、 AS OF内の特定の時間のレコードの状態を取得するために使用されます 副節。それを理解するために、いくつかのクエリを実行してみましょう:
まず、 AS OFを使用してクエリを実行します ” 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'
上のスクリーンショットでわかるように、クエリは「 ListPrice」の値が更新された1行を返しました。 」とProduct_Valid_Toの値 日付の最大値です。
AS OF cを使用して別のクエリを実行してみましょう 「SystemTime=09:10:56: 」。
上のスクリーンショットでわかるように、「 ListPrice」の値は次のようになります。 」は0.00です。
からまで
この句は、
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'
次のスクリーンショットは、クエリ結果を示しています。
と の間
この句は、 FROM ..に似ています。 宛先 句。唯一の違いは、
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'
次のスクリーンショットは、クエリ結果を示しています。
含まれるIN(、)
この副節には、指定された日付範囲内にアクティブになり、終了したレコードが含まれます。アクティブなレコードは含まれません。それを理解するには、「 Contained IN ‘2018-04-20 09:02:25」を使用して以下のクエリを実行します。 ‘から‘ 2018-04-2010: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'
次のスクリーンショットは、クエリ結果を示しています。
シナリオ
組織は在庫管理ソフトウェアを使用しています。その在庫管理ソフトウェアは、システムバージョンの一時テーブルである製品テーブルを使用します。アプリケーションのバグにより、削除された製品はほとんどなく、製品の価格も誤って更新されました。
DBAとして、この問題を調査し、誤って更新されてテーブルから削除されたデータを回復する必要があります。
上記のシナリオをシミュレートするために、いくつかの意味のあるデータを含むテーブルを作成しましょう。 「tblProduct」という名前の新しい一時テーブルを作成します [Production]。[Products]のクローンであるデモデータベースの’ AdventureWorks2014データベースのテーブル。
上記のタスクを実行するために、以下の手順に従いました。
- 「テーブルスクリプトの作成」を抽出しました[本番]。 [製品] AdventureWorks2014データベースから。
- スクリプトからすべての「制約とインデックス」を削除しました。
- 列構造を変更しないでください。
- 一時テーブルに変換するために、SysStartTime列とSysEndTime列を追加しました。
- System_Versioningを有効にしました。
- 指定された履歴テーブル。
- edemoデータベースでスクリプトを実行しました。
以下はスクリプトです:
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
次のスクリプトを実行して、「AdventureWorks2014」データベースの製品テーブルから「DemoDatabase」の製品テーブルにデータをインポートしました。
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
「Thin-JamHexNut」で始まる製品名レコードをtblProductから削除しました。また、「 tblProduct」の名前がFlatWasherで始まる製品の価格も変更しました。 次のクエリを実行して、テーブルを作成します。
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%'
データが削除された時刻を認識しています。したがって、どのデータが削除されたかを識別するために、Contained-IN副節を使用します。上で述べたように、アクティブになり、指定された日付範囲内で終了した行バージョンを持つレコードのリストが表示されます。次に、以下のクエリを実行します:
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)
上記のクエリを実行することにより、22行が取得されました。
含まれている-IN 句は、指定された時間内に更新および削除された行にデータを入力します。
削除されたレコードを入力する:
削除されたレコードにデータを入力するには、Contained-IN句で指定された時間内に更新されたレコードをスキップする必要があります。以下のスクリプトでは、「場所 」句は、 tblProductに存在する製品をスキップします テーブル。次のクエリを実行します:
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)
上記のクエリは、更新されたレコードをスキップしました。したがって、13行を返しました。以下のスクリーンショットを参照してください:
上記の方法を使用することにより、 tblProductから削除された製品のリストを取得できます。 テーブル。
更新されたレコードを入力する
更新されたレコードにデータを入力するには、 Contained-INで指定された時間内に削除されたレコードをスキップする必要があります 句。以下のスクリプトでは、「場所 」句には、 tblProductに存在する製品が含まれます テーブル。次のクエリを実行します:
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)
上記のクエリは、更新されたレコードをスキップしたため、9行を返しました。以下のスクリーンショットを参照してください:
上記の方法を使用すると、間違った値で更新されたレコードと、一時テーブルから削除されたレコードを特定できます。
概要
この記事では、以下について説明しました:
- テンポラルテーブルの高レベルの紹介。
- DMLクエリを実行して期間列を更新する方法について説明しました。
- 一時テーブルから、間違った価格で削除および更新された製品のリストを取得するためのデモ。このレポートは、監査目的で使用できます。