在系统版本控制的时态表中查询数据

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

要获取时间表中最新(当前)数据状态,查询方式与非时间表相同。 如果 PERIOD 列未隐藏,其值会显示在 SELECT * 查询中。 如果你将列指定 PERIODHIDDEN,它们的值不会出现在 SELECT * 查询中。 当 PERIOD 列被隐藏时,请在 SELECT 子句中明确引用这些列,以返回它们的值。

要进行基于时间的分析,使用带有四个时间特定子句的 FOR SYSTEM_TIME 子句来查询当前表和历史表中的数据。 有关这些子句的详细信息,请参阅时态表FROM 子句以及 JOIN、APPLY 和 PIVOT

  • AS OF <date_time>
  • FROM <start_date_time> TO <end_date_time>
  • BETWEEN <start_date_time> AND <end_date_time>
  • CONTAINED IN (<start_date_time>, <end_date_time>)
  • ALL

你可以在查询中为每个表单独指定 FOR SYSTEM_TIME 。 它可用于常见的表表达式、表值函数和存储过程。 当你将表别名与时态表一起使用时,请在时态表名称和别名之间包含 FOR SYSTEM_TIME 子句(请参阅使用 AS OF 子子句查询特定时间中的第二个示例)。

使用 AS OF 子子句查询特定时间

使用 AS OF 子条款来重建数据在过去任何特定时间的状态。 你可以按照列定义中指定的PERIODdatetime2类型精度重建数据。

使用 AS OF 带有常数文字或变量的子子句来动态指定时间条件。 你提供的数值被解读为UTC时间。

第一个例子返回表AS OF的状态dbo.Department,即过去的特定日期。

-- State of entire table AS OF specific date in the past
SELECT [DeptID],
       [DeptName],
       [ValidFrom],
       [ValidTo]
FROM [dbo].[Department] FOR SYSTEM_TIME
    AS OF '2021-09-01 T10:00:00.7230011';

第二个示例对行子集两个时间点之间的值进行比较。

DECLARE @ADayAgo AS DATETIME2;

SET @ADayAgo = DATEADD(DAY, -1, SYSUTCDATETIME());

-- Comparison between two points in time for subset of rows
SELECT D_1_Ago.DeptID,
       d.DeptID,
       D_1_Ago.DeptName,
       d.DeptName,
       D_1_Ago.ValidFrom,
       d.ValidFrom,
       D_1_Ago.ValidTo,
       d.ValidTo
FROM dbo.Department FOR SYSTEM_TIME
    AS OF @ADayAgo AS D_1_Ago
    INNER JOIN Department AS d
         ON D_1_Ago.DeptID = d.DeptID
        AND D_1_Ago.DeptID BETWEEN 1 AND 5;

在时态查询中将视图与 AS OF 子句结合使用

当你需要进行复杂的时间点分析时,视图非常有用。 一个常见的例子是今天生成一份包含上个月数值的业务报告。

通常情况下,客户会有一个规范化的数据库模型,该模型涉及多个具有外键关系的表。 了解该归一化模型中过去某一时刻的数据表现可能很有挑战性,因为所有表格都会独立地以自己的节奏变化。

在这种情况下,最好的选择是创建一个视图并将 AS OF 子子句应用于整个视图。 这种方法将数据访问层的建模与点际分析解耦,因为 SQL Server 透明地将该子句应用AS OF到所有参与视图定义的时序表上。 此外,还可以将时态表和非时态表组合在同一个视图中,AS OF 将仅应用于时态表。 如果视图不引用任何时态表,那么,对其应用时态查询子句会失败并出现错误。

以下示例代码会创建一个联接三个时态表(DepartmentCompanyLocation 以及 LocationDepartments)的视图:

CREATE VIEW [dbo].[vw_GetOrgChart]
AS
SELECT [CompanyLocation].LocID,
       [CompanyLocation].LocName,
       [CompanyLocation].City,
       [Department].DeptID,
       [Department].DeptName
FROM [dbo].[CompanyLocation]
    LEFT OUTER JOIN [dbo].[LocationDepartments]
        ON [CompanyLocation].LocID = LocationDepartments.LocID
    LEFT OUTER JOIN [dbo].[Department]
        ON LocationDepartments.DeptID = [Department].DeptID;
GO

可以使用 AS OF 子子句和 datetime2 文本查询视图:

/* Query view AS OF */
SELECT *
FROM [vw_GetOrgChart] FOR SYSTEM_TIME
    AS OF '2021-09-01 T10:00:00.7230011';

查询特定行随时间推移的变更

当你需要获取当前表格中特定行的所有历史变更(也称为数据审计)时,时间子句 FROM ... TOBETWEEN ... ANDCONTAINED IN 非常有用。

前两个子句返回与特定时期重叠的行版本(即在该时期之前开始、结束于该期间的版本),而 CONTAINED IN 仅返回存在于指定时期边界内的版本。

如果你只搜索非当前的行版本,直接查询历史表以获得最佳查询性能。 可无 ALL 限制地查询当前和历史数据。

/* Query using BETWEEN...AND sub-clause*/
SELECT [DeptID],
       [DeptName],
       [ValidFrom],
       [ValidTo],
       IIF (YEAR(ValidTo) = 9999, 1, 0) AS IsActual
FROM [dbo].[Department] FOR SYSTEM_TIME
    BETWEEN '2021-01-01' AND '2021-12-31'
WHERE DeptId = 1
ORDER BY ValidFrom DESC;

/* Query using CONTAINED IN sub-clause */
SELECT [DeptID],
       [DeptName],
       [ValidFrom],
       [ValidTo]
FROM [dbo].[Department] FOR SYSTEM_TIME
    CONTAINED IN ('2021-04-01', '2021-09-25')
WHERE DeptId = 1
ORDER BY ValidFrom DESC;

/* Query using ALL sub-clause */
SELECT [DeptID],
       [DeptName],
       [ValidFrom],
       [ValidTo],
       IIF (YEAR(ValidTo) = 9999, 1, 0) AS IsActual
FROM [dbo].[Department] FOR SYSTEM_TIME ALL
ORDER BY [DeptID], [ValidFrom] DESC;

为了总结数据在一段时间内的表现,结合FOR SYSTEM_TIME BETWEEN ... ANDGROUP BY并汇总函数。 将此技术用于描述性统计和趋势分析。 它会汇总在该时间窗口内处于活动状态的每个行版本,而不是重建某一时间点的状态。

以下示例计算了员工在某个时间窗口内按部门分组的薪资描述性统计数据。 它使用在时态表时态表使用场景中定义的 Employee 系统版本控制表。 AnnualSalary列是一个非常适合 AVGMINMAXSTDEV 函数的数值度量。

FOR SYSTEM_TIME BETWEEN ... AND 子句返回与该时间段重叠的所有行版本。 因此,汇总数据包括窗口期内所有生效的薪资。 当员工的薪资发生变化时,查询会返回该员工的多个版本,每个版本都会贡献到聚合数据中。

DECLARE @periodStart AS DATETIME2 = '2021-01-01';
DECLARE @periodEnd AS DATETIME2 = '2021-12-31';

SELECT [Department],
       AVG([AnnualSalary]) AS AvgSalary,
       MIN([AnnualSalary]) AS MinSalary,
       MAX([AnnualSalary]) AS MaxSalary,
       STDEV([AnnualSalary]) AS SalaryStdDev,
       COUNT(*) AS RowVersions
FROM [dbo].[Employee] FOR SYSTEM_TIME
    BETWEEN @periodStart AND @periodEnd
GROUP BY [Department]
ORDER BY AvgSalary DESC;