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

其他参考: 临时表注意事项和限制 、 临时表(Temporal tables)


猜你喜欢

转载自blog.csdn.net/kk185800961/article/details/80468322