1. 程式人生 > >SQL Server 2016 版本由系統控制的臨時表(Temporal tables)

SQL Server 2016 版本由系統控制的臨時表(Temporal tables)

臨時表是 ANSI SQL 2011 中引入的資料庫功能。版本由系統控制的臨時表是使用者表的一種型別,旨在保留完整的資料更改歷史記錄,並實現輕鬆的時間點分析。 這種型別的臨時表之所以稱為版本由系統控制的臨時表,是因為每一行的有效期由系統(即資料庫引擎)管理。

(官方文件直接翻譯為臨時表,為了區分這類臨時表 #table_name,以下直接稱呼 Temporal tables )

Temporal tables 與 變更資料捕獲(CDC)及 更改跟蹤(Chang Tracking)有類似,但 Temporal tables 記錄是作為一個事務提交的,並且記錄所有欄位變更的行歷史值(不記錄當前的)。(與 CDC及CT 的區別,參考:

比較變更資料捕獲和更改跟蹤

Temporal tables 是對單個表定義的。除了這些期限列以外,Temporal tables 還包含對使用映象架構的另一個表的引用。 每當更新或刪除了臨時表中的某行後,系統將使用此表來自動儲存該行的先前版本。 此附加表稱為歷史記錄表,而儲存當前(實際)行版本的主表稱為當前表,或直接稱為臨時表(Temporal tables)

Temporal tables 的工作原理:

表的系統版本控制是以一對錶(當前表和歷史記錄表)的形式實現的。 在其中每個表中,以下兩個附加 datetime2 列用於定義每行的有效期:
* 期限開始列:系統在此列(通常表示為 SysStartTime 列)中記錄行的開始時間。
* 期限結束列:系統在此列(通常表示為 SysEndTime 列)中記錄行的結束時間。
當前表包含每個行的當前值。 歷史記錄表包含每個行的每個先前值(如果有),以及該行生效的開始時間和結束時間。


示例:

CREATE TABLE [dbo].[Employee](
	[EmployeeID] [int] NOT NULL,
	[Name] [nvarchar](100) NOT NULL,
	[Position] [varchar](100) NOT NULL,
	[Department] [varchar](100) NOT NULL,
	[Address] [nvarchar](1024) NOT NULL,
	[AnnualSalary] [decimal](10, 2) NOT NULL,
	[SysStartTime] datetime2 (2) GENERATED ALWAYS AS ROW START,
	[SysEndTime] datetime2 (2) GENERATED ALWAYS AS ROW END,
	PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime]),
	PRIMARY KEY CLUSTERED ([EmployeeID] ASC)
) 
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory))
GO

1. 由系統版本的臨時表必須已定義主鍵,並且已定義且只定義了一個 PERIOD FOR SYSTEM_TIME (其中包含兩個 datetime2 列,宣告為 GENERATED ALWAYS AS ROW START / END)

2. PERIOD 列始終不可為 null,即使未指定是否為 null,也是如此。 如果將 PERIOD 列顯式定義為可為 null,則 CREATE TABLE 語句將失敗。

3. 歷史記錄表必須在列數、列名、排序和資料型別方面始終與當前表或臨時表架構一致。

4. 將在當前表或臨時表所在的架構中自動建立匿名歷史記錄表。

5. 匿名歷史記錄表名稱採用以下格式:MSSQL_TemporalHistoryFor_<current_temporal_table_object_id>_[suffix](suffix 為字尾)。 字尾是可選的,僅當表名的第一部分不唯一時才會新增它。

6. 歷史記錄表將建立為行儲存表。 如果可能,將應用頁壓縮,否則歷史記錄表將不會進行壓縮。 例如,某些表配置(如稀疏列)不允許壓縮。

7. 將為歷史記錄表(名稱自動生成,格式為 IX_<history_table_name>)建立一個預設聚集索引。 聚集索引包含 PERIOD 列(結束、開始)。

測試:

INSERT [dbo].[Employee] ([EmployeeID], [Name], [Position], [Department], [Address], [AnnualSalary]) 
SELECT 1, N'AA', N'DBA', N'DepA', N'BeiJing', 2000 UNION ALL
SELECT 2, N'BB', N'DBB', N'DepB', N'ShenZhen',1500 UNION ALL
SELECT 3, N'CC', N'DBC', N'DepA', N'ShenZhen',1600 UNION ALL
SELECT 4, N'DD', N'DBD', N'DepB', N'ShangHai',1000 UNION ALL
SELECT 5, N'EE', N'DBE', N'DepA', N'BeiJing', 1000
GO

--新增欄位
ALTER TABLE [dbo].[Employee] ADD Col INT NOT NULL CONSTRAINT DF_Employee_Col DEFAULT(0)
GO
INSERT [dbo].[Employee] ([EmployeeID], [Name], [Position], [Department], [Address], [AnnualSalary],Col) 
SELECT 6, N'FF', N'DBG', N'DepA', N'ShenZhen', 1800,100
GO
DELETE FROM [dbo].[Employee] WHERE [EmployeeID] = 2
GO
UPDATE [dbo].[Employee] SET Col=4,[Name]='kk' WHERE [EmployeeID] = 4
GO
--刪除欄位(歷史表也同樣刪除)
ALTER TABLE [dbo].[Employee] DROP CONSTRAINT DF_Employee_Col
GO
ALTER TABLE [dbo].[Employee] DROP COLUMN Col
GO

SELECT * FROM [dbo].[Employee]
SELECT * FROM [dbo].[EmployeeHistory]


由於跟蹤記錄會越來越多,對於比較老的無效資料,可以定期刪除,僅當 SYSTEM_VERSIONING = OFF 時才能操作,如將刪除 30 天以前的資料:

ALTER TABLE dbo.[Employee] SET (SYSTEM_VERSIONING = OFF);  
GO
DELETE FROM [dbo].[EmployeeHistory] WHERE [SysEndTime] < DATEADD (DAY, -30, SYSUTCDATETIME ())
GO
ALTER TABLE dbo.[Employee] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory))
GO
為了避免資料不一致,請在維護時段(修改資料的工作負載處於非活動狀態)或在事務中(有效阻止其他工作負載)執行清理。 此操作需要對當前和歷史記錄表擁有 CONTROL 許可權。

其他更改為 Temporal tables 的方法:

--1. 全新建立,不指定歷史表
CREATE TABLE [dbo].[Employee01](
	[EmployeeID] [int] NOT NULL IDENTITY(1,1),
	[Name] [nvarchar](100) NOT NULL,
	[SysStartTime] datetime2 (2) GENERATED ALWAYS AS ROW START,
	[SysEndTime] datetime2 (2) GENERATED ALWAYS AS ROW END,
	PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime]),
	PRIMARY KEY CLUSTERED ([EmployeeID] ASC)
) 
WITH (SYSTEM_VERSIONING = ON)
GO
/* 生成表如下,歷史表命名字尾比較隨機
[dbo].[Employee01]
[dbo].[MSSQL_TemporalHistoryFor_1957582012]
*/

--2. 全新建立,指定歷史表(歷史表當前未存在)
CREATE TABLE [dbo].[Employee02](
	[EmployeeID] [int] NOT NULL IDENTITY(1,1),
	[Name] [nvarchar](100) NOT NULL,
	[SysStartTime] datetime2 (2) GENERATED ALWAYS AS ROW START,
	[SysEndTime] datetime2 (2) GENERATED ALWAYS AS ROW END,
	PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime]),
	PRIMARY KEY CLUSTERED ([EmployeeID] ASC)
) 
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory02))
GO
/* 生成表如下:
[dbo].[Employee02]
[dbo].[EmployeeHistory02]
*/

--3. 對於已存在的表,且是空表,將其改為歷史版本表
CREATE TABLE [dbo].[Employee03](
	[EmployeeID] [int] NOT NULL IDENTITY(1,1),
	[Name] [nvarchar](100) NOT NULL,
	PRIMARY KEY CLUSTERED ([EmployeeID] ASC)
) 
GO
ALTER TABLE [dbo].[Employee03]
ADD [SysStartTime] DATETIME2 GENERATED ALWAYS AS ROW START,
    [SysEndTime] DATETIME2 GENERATED ALWAYS AS ROW END,
    PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime])
GO
ALTER TABLE [dbo].[Employee03]
SET ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory03));
GO
/* 生成表如下:
[dbo].[Employee03]
[dbo].[EmployeeHistory03]
*/

--4. 對於已存在的表,且是非空表,將其改為歷史版本表(最好檢查資料一致性 :DATA_CONSISTENCY_CHECK )
CREATE TABLE [dbo].[Employee04](
	[EmployeeID] [int] NOT NULL IDENTITY(1,1),
	[Name] [nvarchar](100) NOT NULL,
	PRIMARY KEY CLUSTERED ([EmployeeID] ASC)
) 
GO
INSERT INTO [dbo].[Employee04]([Name]) VALUES('AA'),('BB'),('CC')
GO
ALTER TABLE [dbo].[Employee04]
ADD [SysStartTime] DATETIME2 NOT NULL CONSTRAINT DF_SysStartTime_04 DEFAULT DATEADD(SECOND, -1, SYSUTCDATETIME()),
    [SysEndTime] DATETIME2 NOT NULL CONSTRAINT DF_SysEndTime_04 DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999')
GO
ALTER TABLE [dbo].[Employee04] ADD PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime]);
GO
ALTER TABLE [dbo].[Employee04] SET ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory04 , DATA_CONSISTENCY_CHECK = ON));
GO
ALTER TABLE [dbo].[Employee04] DROP CONSTRAINT DF_SysStartTime_04
GO
ALTER TABLE [dbo].[Employee04] DROP CONSTRAINT DF_SysEndTime_04
GO
/*生成表如下:
[dbo].[Employee04]
[dbo].[EmployeeHistory04]
*/


--5. 建立隱藏的列(欄位 SysStartTime 和 SysEndTime 隱藏,在當前表看不到,歷史表可以看到)
CREATE TABLE [dbo].[Employee00](
	[EmployeeID] [int] NOT NULL IDENTITY(1,1),
	[Name] [nvarchar](100) NOT NULL,
	[SysStartTime] datetime2 (2) GENERATED ALWAYS AS ROW START HIDDEN,
	[SysEndTime] datetime2 (2) GENERATED ALWAYS AS ROW END HIDDEN,
	PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime]),
	PRIMARY KEY CLUSTERED ([EmployeeID] ASC)
) 
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory00))
GO

在系統版本控制臨時表中查詢資料


下圖顯示了一個 Employee 表方案,其資料樣本包括當前行版本(標記為藍色)以及歷史行版本(標記為灰色)。圖的右側部分在時間軸上顯示了行版本,以及在使用或不使用 SYSTEM_TIME 子句的情況下,你針對不同型別的基於臨時表的查詢選擇了哪些行。


歷史版本查詢方法:

--某個時刻表的快照記錄:
SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME AS OF '2018-05-27 05:45:54.55' 

SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME ALL 
WHERE SysStartTime <= '2018-05-27 05:45:54.55' AND SysEndTime > '2018-05-27 05:45:54.55'


--指定時間氛圍內曾經活動的所有行記錄
SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME FROM  '2018-05-27 05:45:54.55' TO '2018-05-27 06:12:46.71'

SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME ALL 
WHERE SysStartTime < '2018-05-27 05:45:54.55' AND SysEndTime > '2018-05-27 05:45:54.55'

SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME BETWEEN '2018-05-27 05:45:54.55' AND '2018-05-27 06:12:46.71'

SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME ALL 
WHERE SysStartTime <= '2018-05-27 05:45:54.55' AND SysEndTime > '2018-05-27 05:45:54.55'

--歷史版本記錄
SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME CONTAINED IN ('2018-05-27 05:45:54.55', '2018-05-27 06:12:46.71')

SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME ALL 
WHERE SysStartTime >= '2018-05-27 05:45:54.55' AND SysEndTime <= '2018-05-27 06:12:46.71'

--當前表和歷史表記錄彙總
SELECT * FROM [dbo].[Employee] FOR SYSTEM_TIME ALL