临时表

适用于: SQL Server 2016 (13.x) 及以后版本 Azure SQL 数据库Azure SQL 托管实例Microsoft Fabric 中的 SQL 数据库

时态表(也称为系统版本控制的时态表)是数据库的一项功能,内置支持提供表中存储的数据在任意时间点的信息,而不只是当前时刻的正确数据。

开始使用系统版本控制的时态表,并查看时态表使用场景

什么是系统版本控制的时态表?

系统版本控制的时态表是用户表的一种类型,旨在保留完整的数据更改历史记录,并轻松实现时间点分析。 这种类型的临时表之所以称为版本由系统控制的临时表,是因为每一行(即数据库引擎)的有效期由系统管理。

每个临时表有两个显式定义的列,其中每个列都有一个 datetime2 数据类型。 这些列被称为周期列。 每当修改了某行后,系统将以独占方式使用这些期限列来记录每行的有效期。 存储当前数据的主表称为当前表,或简称为时态表 。

除了这些时间段列以外,时态表还包含对另一个具有镜像架构的表的引用,该表称为历史表。 每当更新或删除了时态表中的某行后,系统将使用历史记录表来自动存储该行的先前版本。 在创建时态表期间,可以指定现有的历史记录表(必须与架构相符),或者让系统创建默认的历史记录表。

为什么是时间性的?

实际的数据源是动态的,业务决策多半依赖于分析师从数据演变中获得的见解。 临时表的用例包括:

  • 在必要时审核所有数据变更并执行数据取证
  • 重构数据在过去任意时间之前的状态
  • 计算各时间段的趋势
  • 为决策支持应用程序保持一个慢速变化的维度
  • 在发生意外的数据更改和应用程序错误后进行恢复

Temporal 是如何工作的?

表的系统版本控制是以一对表(当前表和历史记录表)的形式实现的。 在其中每个表中,两个额外的 datetime2 列用于定义每行的有效时间段:

  • 期间起始列:系统会在此列中记录该行的开始时间,通常记为 ValidFrom 列。

  • 时间段结束时间列:系统在此列(通常表示为 列)中记录行的结束时间。ValidTo

当前表包含每个行的当前值。 历史记录表包含每个行的每个先前值(旧版本)(如果有),以及该行生效的开始时间和结束时间。

显示时态表工作方式的示意图。

以下脚本演示了包含员工信息的场景:

CREATE TABLE dbo.Employee
(
    [EmployeeID] INT NOT NULL PRIMARY KEY CLUSTERED,
    [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,
    [ValidFrom] DATETIME2 GENERATED ALWAYS AS ROW START,
    [ValidTo] DATETIME2 GENERATED ALWAYS AS ROW END,
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory));

有关详细信息,请参阅创建系统版本控制的时态表

  • 插入:系统基于系统时钟将 ValidFrom 列的值设置为当前事务的开始时间(采用 UTC 时间),并将 ValidTo 列的值指定为最大值 9999-12-31。 这会将该行标记为展开状态。

  • 更新:系统将行的先前值存储在历史记录表中,并基于系统时钟将 ValidTo 列的值设置为当前事务的开始时间(采用 UTC 时间)。 这会将该行标记为已关闭,并记录该行有效的时间段。 在当前表中,将使用新值更新行,同时,系统会基于系统时钟将 ValidFrom 列的值设置为事务的开始时间(采用 UTC 时间)。 在当前表中,ValidTo 列的更新行值将为最大值 9999-12-31

  • 删除:系统将行的先前值存储在历史记录表中,并基于系统时钟将 ValidTo 列的值设置为当前事务的开始时间(采用 UTC 时间)。 这会将该行标记为已关闭,并记录前一行的有效期间。 在当前表中,该行将被删除。 对当前表的查询不会返回此行。 只有处理历史数据的查询才会返回已关闭行的数据。

  • 合并:其行为完全如同最多执行了三个语句(INSERTUPDATE 和/或 DELETE),具体取决于 MERGE 语句中将哪些内容指定为操作。

系统 datetime2 列中记录的时间基于事务本身的开始时间。 例如,在单个事务中插入的所有行,在对应于 SYSTEM_TIME 周期开始时间的列中记录的 UTC 时间都相同。

在时态表上运行任何数据修改查询时,即使没有列值更改,数据库引擎也会向历史记录表添加一行。

如何查询临时数据?

SELECT ... FROM <table> 语句新增了子句 FOR SYSTEM_TIME,其中包含五个时态特定子子句,用于跨当前表和历史表查询数据。 这种新的 SELECT 语句语法可直接用于单个表,也可通过多个联接传播,并可用于基于多个时态表的视图。

通过使用五个子子句之一的 FOR SYSTEM_TIME 子句进行查询时,将包括时态表中的历史数据,如下图所示。

展示时态查询如何工作的示意图。

以下查询用于搜索某位员工的行版本,这些行版本满足筛选条件 WHERE EmployeeID = 1000,并且在 2021 年 1 月 1 日至 2022 年 1 月 1 日期间的至少一部分时间内处于有效状态(包括上边界):

SELECT * FROM Employee
    FOR SYSTEM_TIME
        BETWEEN '2021-01-01 00:00:00.0000000' AND '2022-01-01 00:00:00.0000000'
            WHERE EmployeeID = 1000 ORDER BY ValidFrom;

FOR SYSTEM_TIME 会筛选出有效期持续时间为零 (ValidFrom = ValidTo) 的行。

如果对同一事务中的同一个主键执行多项更新,将生成这些行。 在这种情况下,时态查询只会返回发生事务之前的行版本,以及发生事务之后的当前行。

如果需要在分析中包括这些行,请直接查询历史记录表。

在下表中,“符合条件的行”列中的 ValidFrom 表示正在查询的表中 ValidFrom 列的值,ValidTo 表示正在查询的表中 ValidTo 列的值。 如需了解完整语法和示例,请参阅 FROM 子句以及 JOIN、APPLY 和 PIVOT,以及 在系统版本化时态表中查询数据

Expression 符合条件的行 Note
AS OF date_time ValidFrom <= date_timeAND ValidTo >date_time 返回一个表,其行中包含过去指定时间点的当前值。 在内部,会在时态表及其历史表之间执行联合操作。 结果会经过筛选,以返回在由 date_time 参数指定的时间点有效的行中的值。 如果 system_start_time_column_name 值小于或等于 date_time 参数值,并且 system_end_time_column_name 值大于 date_time 参数值,则此行的值视为有效。
FROM start_date_timeTOend_date_time ValidFrom < end_date_timeAND ValidTo >start_date_time 返回一个表,其中包含在指定的时间范围内保持活动状态的所有行版本的值,不管这些版本是在 自变量的 start_date_timeFROM 参数值之前开始活动,还是在 自变量的 end_date_time TO参数值之后停止活动。 在内部,将在时态表与其历史记录表之间进行联合。 筛选结果,以返回在指定时间范围内任意时间保持活动状态的所有行版本的值。 正好在 FROM 终结点定义的下限时间停止活动状态的行将被排除,正好在 TO 终结点定义的上限时间开始活动状态的记录也将被排除。
BETWEEN start_date_timeANDend_date_time ValidFrom <= end_date_timeAND ValidTo >start_date_time 与上面的 FOR SYSTEM_TIME FROMstart_date_timeTOend_date_time 描述相同,不过,返回的行表会包括在 end_date_time 终结点定义的上限时间变为活动状态的行。
CONTAINED INstart_date_timeend_date_time ValidFrom >= start_date_timeAND ValidTo <=end_date_time 返回一个表,其中包含在 CONTAINED IN 自变量的两个时间段值定义的指定时间范围内打开和关闭的所有行版本的值。 恰好在下边界变为活动状态的行,或者恰好在上边界不再处于活动状态的行,也会被包含在内。
ALL 所有行 返回属于当前表和历史记录表的行的并集。

隐藏时间段列

可以选择隐藏时间段列,这样未显式引用它们的查询就不会返回这些列(例如,在运行 SELECT * FROM <table> 时)。

若要返回隐藏的列,必须在查询中显式引用隐藏的列。 同样,如果这些新的时间段列不存在,INSERTBULK INSERT 语句将会继续(并且列值将自动填充)。

有关使用 HIDDEN 子句的详细信息,请参阅 CREATE TABLEALTER TABLE

Samples

  • ASP.NET:请参阅 ASP.NET Core Web 应用程序,了解如何使用时态表构建时态应用程序。

  • AdventureWorks 示例数据库:可以下载适用于 SQL Server 的 AdventureWorks 数据库,其中包括时态表功能。