不知各位有沒有注意到, 在 SSMS 物件總管視窗 裡 SQL Server 2016 的範例資料庫 (WideWorldImporters) 的剖份 table, 會被標註為 (由系統控制版本), 這個到底是什麼呢? 您可以試著把它點開, 它下面又掛了一個 table, 標註為 (記錄), 如下圖的 Cities 與 Cities_Archive. 其實, 後者 Cities_Archive 就是一個 System-Versioned Temporal Table, 綁定在前者 Cities 身上.
茲分為以下幾個部份及實作步驟進行說明:
1.. 何謂 System-Versioned Temporal Table?
Temporal (時態性) 是 ANSI SQL 2011 納入的資料庫功能.
Temporal Table 是 SQL Server 實作的方式, 它會保留資料變更的所有歷史異動記錄. 可以拿來作資料異動追蹤, 或資料異動趨勢分析. 至於為何是 System-Versioned, 主要是因為每個資料列的有效期間是由系統 (也就是資料庫引擎) 所管理. 什麼是資料列有效期間呢? 我想, 我們看範例會比較容易了解. 為了避免文章過長, 以下的內容, 會統一簡稱為 Temporal Table.
Temporal Table 是 SQL Server 實作的方式, 它會保留資料變更的所有歷史異動記錄. 可以拿來作資料異動追蹤, 或資料異動趨勢分析. 至於為何是 System-Versioned, 主要是因為每個資料列的有效期間是由系統 (也就是資料庫引擎) 所管理. 什麼是資料列有效期間呢? 我想, 我們看範例會比較容易了解. 為了避免文章過長, 以下的內容, 會統一簡稱為 Temporal Table.
2.. 建立測試資料庫
DROP DATABASE IF EXISTS TemporalSample; GO CREATE DATABASE TemporalSample; GO
3.. 針對現有的資料表, 加上 Temporal Table 的功能
(1) 建立不含 Temporal Table 的資料表, 同時新增 2 筆資料
USE TemporalSample; GO DROP TABLE IF EXISTS [dbo].[Department] GO CREATE TABLE [dbo].[Department] ( [ID] INT NOT NULL , [Name] NVARCHAR(20) NOT NULL CONSTRAINT PK_Department PRIMARY KEY CLUSTERED ( [ID] ) ); GO INSERT INTO [dbo].[Department] VALUES (1, 'MIS') , (2, 'HR'); GO SELECT * FROM [dbo].[Department]; GO
(2) 為 [dbo].[Department] 加上 Temporal Table 的功能
A.. 加入了 ValidFrom 及 ValidateTo 這 2 個欄位. 資料型態是 DATETIME2(2), 且存放的是 UTC 的時間.
B.. ValidFrom:
* GENERATED ALWAYS AS ROW START --> 資料列的起始效期
* HIDDEN --> SELECT * 看不到, 必須要 SELECT 個別欄位, 且指定 ValidFrom 才能看到
* CONSTRAINT DEFAULT --> 給予預設值 (for 現行資料及後續的新增資料)
C.. ValidTo:
* GENERATED ALWAYS AS ROW END --> 資料列的結束效期
* HIDDEN --> SELECT * 看不到, 必須要 SELECT 個別欄位, 且指定 ValidTo 才能看到
* CONSTRAINT DEFAULT --> 給予預設值 (for 現行資料及後續的新增資料) (預設為最大的時間值)
D.. PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo) : 代表要以 ValidFrom 與 ValidTo 這 2 個欄位, 作為效期的起始及終止
E.. SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.DepartmentHistory)) : 啟用 Temporal Table, 其歷史資料檔為 DepartmentHistory.
/* Turn ON system versioning in Department table in two steps (1) add new period columns (HIDDEN) (2) create default history table */ ALTER TABLE [dbo].[Department] ADD ValidFrom DATETIME2 (2) GENERATED ALWAYS AS ROW START HIDDEN CONSTRAINT DF_ValidFrom DEFAULT DATEADD(SECOND, -1, SYSUTCDATETIME()) , ValidTo DATETIME2 (2) GENERATED ALWAYS AS ROW END HIDDEN CONSTRAINT DF_ValidTo DEFAULT '9999.12.31 23:59:59.99' , PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo); GO ALTER TABLE [dbo].[Department] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.DepartmentHistory)); GO
並截圖如下, 以標示重點.
SELECT * FROM [dbo].[Department]; GO
要特別指定才能看到
SELECT ID, Name, ValidFrom, ValidTo FROM [dbo].[Department]; GO
並截圖如下, 以標示重點.
看一下 SSMS 物件總管視窗, 可以發現它的圖示有變, 並加上對應的標註
4.. 建立新的資料表, 同時加上 Temporal Table 的功能
(1) 建立 Temporal Table 的語法, 大致與前面相同, 不再贅述
(2) 關於移除 Temporal Table 功能的部份, 可以參以下的 ---- Code Block 1 ---- 的區塊
USE TemporalSample; GO ---- Code Block 1 ---- (BEGIN) IF OBJECT_ID('dbo.Employee') IS NOT NULL ALTER TABLE [dbo].[Employee] SET ( SYSTEM_VERSIONING = OFF) ; GO DROP TABLE IF EXISTS [dbo].[Employee] GO DROP TABLE IF EXISTS [dbo].[EmployeeHistory] GO ---- Code Block 1 ---- (END) CREATE TABLE Employee ( [ID] INT NOT NULL PRIMARY KEY CLUSTERED , [Name] NVARCHAR(50) NOT NULL , [Position] NVARCHAR(50) NOT NULL , [DeptID] INT NOT NULL , [Address] NVARCHAR(100) NOT NULL , [AnnualSalary] DECIMAL(10,2) NOT NULL , [ValidFrom] DATETIME2 (2) GENERATED ALWAYS AS ROW START , [ValidTo] DATETIME2 (2) GENERATED ALWAYS AS ROW END , PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo) ) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory)); GO
看一下 SSMS 物件總管視窗, 可以發現它的圖示有變, 並加上對應的標註
試一下移除 Temporal Table 功能的 程式段
IF OBJECT_ID('dbo.Employee') IS NOT NULL ALTER TABLE [dbo].[Employee] SET ( SYSTEM_VERSIONING = OFF) ; GO
看一下 SSMS 物件總管視窗, Employee 與 EmployeeHistory 均成為一般的 table
為了後續的測試, 還是先回復一下 Temporal Table 的功能
ALTER TABLE [dbo].[Employee] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory)); GO
5.. 新增 2 筆資料
USE [TemporalSample] GO INSERT INTO [dbo].[Employee] ([ID], [Name], [Position], [DeptID], [Address], [AnnualSalary] ) VALUES (1, N'jasper', N'engineer', 1, N'taipei', 1500) , (2, N'joseph', N'manager', 1, N'taipei', 3000) GO
查一下資料, Employee 有 2 筆資料, 其 ValidTo 的內容為 DATETIME2 的最大值. 但 EmployeeHistory 是沒有資料的, 因為 EmployeeHistory 是存放修改前的資料, 但 INSERT 的動作, 並沒有修改前的資料
SELECT * FROM [dbo].[Employee] GO SELECT * FROM [dbo].[EmployeeHistory] GO
6.. 修改 1 筆資料
USE TemporalSample; GO UPDATE A SET A.[AnnualSalary] = 4000 FROM [dbo].[Employee] A WHERE A.[ID] = 2; GO
可以發現, EmployeeHistory 保留了修改前的資料內容, 最新的資料, 還是在 Employee 資料表.
7.. 刪除 1 筆資料
USE TemporalSample; GO DELETE A FROM [dbo].[Employee] A WHERE A.[ID] = 2; GO
可以發現, EmployeeHistory 保留了被刪除的資料內容, 該筆的 ValidTo 欄位, 代表的是刪除的時間點.
8.. 查 Temporal Table 的一些語法
當然可以直接查 EmployeeHistory 資料表, 但 SQL Server 也提供了由 Employee 查詢的方式, 可以細讀 "參考文件 8.."
USE [TemporalSample] GO SELECT [ID] ,[Name] ,[Position] ,[DeptID] ,[Address] ,[AnnualSalary] ,[ValidFrom] ,[ValidTo] FROM [dbo].[Employee] --(1) FOR SYSTEM_TIME ALL ORDER BY [ValidFrom]; --(2) --FOR SYSTEM_TIME BETWEEN '2017-12-07 T06:35:00' AND '2017-12-08 T00:00:00'; --FOR SYSTEM_TIME FROM '2017-12-07 T06:35:00' TO '2017-12-08 T00:00:00'; --(3).A --FOR SYSTEM_TIME CONTAINED IN ('2017-12-07 T06:35:00','2017-12-08 T00:00:00'); --(3).B --FOR SYSTEM_TIME CONTAINED IN ('2017-12-07 T00:00:00','2017-12-08 T00:00:00'); --(4) --FOR SYSTEM_TIME AS OF '2017-12-07 06:33:00.00'; GO
(1) ALL : 查出全部
<for clause> 語法範例:
FOR SYSTEM_TIME ALL ORDER BY [ValidFrom];
FOR SYSTEM_TIME ALL ORDER BY [ValidFrom];
(2) BETWEEN ... AND ....; FROM ... TO ... 說明 : 適用於想要取歷史與最新資料的狀況
只要資料的 起始時間 至 結束時間, 與 指定的區間條有 overlap 即符合條件; 所以只查出 2 筆
<for clause> 語法範例:
FOR SYSTEM_TIME BETWEEN '2017-12-07 T06:35:00' AND '2017-12-08 T00:00:00';
FOR SYSTEM_TIME FROM '2017-12-07 T06:35:00' TO '2017-12-08 T00:00:00';
(3) CONTAINED IN : 適用於想要取歷史資料的狀況
只要資料的 起始時間 至 結束時間, 必須完全落在 指定的區間條, 才符合條件.
CASE A: 回傳 0 筆資料
<for clause> 語法範例:
FOR SYSTEM_TIME CONTAINED IN ('2017-12-07 T06:35:00','2017-12-08 T00:00:00');
CASE B: 回傳 2 筆資料
<for clause> 語法範例:
FOR SYSTEM_TIME CONTAINED IN ('2017-12-07 T00:00:00','2017-12-08 T00:00:00');
(4) AS OF : 資料列起始時間 <= 查詢修件 < 資料列結束時間
<for clause> 語法範例:
FOR SYSTEM_TIME AS OF '2017-12-07 06:33:00.00';
9.. 適用情境
以下茲舉例, 有興趣者, 可以細讀 "參考文件 3.."
(1) Data Audit : 資料稽核 --> 資料被誰, 在那個時點, 改成什麼值, 例如: 誰在某個時點改了客戶資料
(2) Point in Time Analysis (Time Travel) : 時間點分析 --> 資料依照時間的分析, 例如: 存貨量依時間的異動趨勢. 亦即 庫存量=f(時間點)
(3) Anomaly Detection : 異常偵測 --> 例如: 某個時間的產品銷售量, 突然變高或變低
(4) Repairing Row-Level Data Corruption : 修復遭到改錯或刪除的資料 --> 例如: 今天某個使用者誤刪資料, 則可以到 Temporal Table 找出被刪掉的那一筆, 予以還原
10.. 結論
(1) Change Data Capture 存放的時間預設為 3 天; Temporal Table 存放的時間, 則可以永久
(2) Change Data Capture 主要用在 ETL 的情境; Temporal Table 主要用在 資料稽核 / 時間點分析 / 異常偵測 / 修復遭到改錯或刪除的資料
至於其它的差異, 茲節錄如下, 可以細讀 "參考文件 7.."
11.. 參考文件
1.. Microsoft Docs, "Temporal Tables"
2.. Microsoft Docs, "Getting Started with System-Versioned Temporal Tables"
3.. Microsoft Docs, "Temporal Table Usage Scenarios"
4.. Microsoft Docs, "Querying Data in a System-Versioned Temporal Table"
--> 這3篇, 有將 System-Versioned Temporal Tables 與 Change Data Capture / Change Tracking 作了比較
8.. SQL Central, "Time Traveling with Temporal Tables on SQL Server 2016"
--> 這篇提到如何進行 Temporal Table 的查詢 (透過被其綁定的 table)
A.. StackOverflow, "Will Temporal Tables be included in the Standard Edition of SQL Server 2016?"
--> 這篇提到 System Versioned Table (Temporal Table) 在每個 SQL Server 2016 Edition 都支援 (Enterprise, Standard, Web, Express, Developer)
沒有留言:
張貼留言