视图中的系统版本化(临时)表

Posted

技术标签:

【中文标题】视图中的系统版本化(临时)表【英文标题】:System Versioned (Temporal) tables in a view 【发布时间】:2018-07-12 05:34:24 【问题描述】:

我有许多加入的“系统版本”表,例如人员、电话号码和电子邮件地址 Person 一次只能有一个 PhoneNumber 和一个 EmailAddress。

PhoneNumber 和 EmailAddress通常不会在一次更新所有 3 个的事务之外更新。 (但它们可以独立更新,只是不是在正常情况下) 例如。如果我更改电话号码,则所有 3 条记录将在同一事务中更新,因此在历史记录表中为它们提供相同的“开始时间”。

假设我插入一个人,然后在 2 次交易中更改此人的姓名、电子邮件地址和电话号码:

DECLARE @Id TABLE(ID INT)
DECLARE @PersonId INT

-- Initial insert
BEGIN TRANSACTION
    INSERT INTO Person (Name) OUTPUT inserted.PersonId INTO @Id VALUES ('Homer') 
    SELECT @PersonId = Id FROM @Id
    INSERT INTO EmailAddress (Address, PersonId) VALUES ('homer@fake', @PersonId)
    INSERT INTO PhoneNumber (Number, PersonId) VALUES ('999', @PersonId)
COMMIT TRANSACTION

-- Update 
WAITFOR DELAY '00:00:02'

BEGIN TRANSACTION
    UPDATE Person SET Name = 'Kwyjibo' WHERE PersonID = @PersonId
    UPDATE EmailAddress SET Address = 'kwyjibo@fake'  WHERE PersonID = @PersonId
    UPDATE PhoneNumber SET Number = '000'  WHERE PersonID = @PersonId
COMMIT TRANSACTION

现在我使用时间查询从视图中选择(只是表的内部连接):

SELECT * FROM vwPerson FOR SYSTEM_TIME ALL 
WHERE PersonId = @PersonId
ORDER BY SysStartTime DESC

每次编辑组合都会返回一行!

如何查询此视图(如果可能的话)以仅返回 1 行用于在同一事务中进行的更新? 我可以添加一个 WHERE 子句来匹配所有 SysStartTimes,但是这将排除那些独立于其他 2 个表更新的情况。

【问题讨论】:

临时表不会在每个事务上更改 - 它们会在每个 UPDATEDELETE 上更改 - 即使 UPDATE 没有更改任何内容(例如 UPDATE foo SET col = 'A' WHERE col = 'A' 仍会导致行被添加到历史表中)。听起来您想剔除历史记录表以减少“嘈杂”的行,在这种情况下,这是一个完全不同的问题。 不,我对“嘈杂”的行很满意(即没有更新任何内容的更新)。我不满意的是查询常规视图不会返回单行来更新同时发生的所有 3 个表(SysStartTime 在单个事务中保证是相同的)。看到结果的第一行是它的结束方式,接下来的 3 行是不同状态的组合。我希望它足够聪明,能够意识到它都是同一更新事务的一部分,而不是显示第 2 到 4 行 【参考方案1】:

由于是独立更新,实际上您首先必须“重建”一个时间线,您可以将数据加入到该时间线中。下面是一个“草图”,显然没有你的实际表定义如此未经测试:

;WITH AllTimes as (
     SELECT PersonId,SysStartTime as ATime FROM Person
     UNION
     SELECT PersonId,SysEndTime FROM Person
     UNION
     SELECT PersonId,SysStartTime FROM EmailAddress
     UNION
     SELECT PersonId,SysEndTime FROM EmailAddress
     UNION
     SELECT PersonId,SysStartTime FROM PhoneNumber
     UNION
     SELECT PersonId,SysEndTime FROM PhoneNumber
), Ordered as (
     SELECT
        PersonId, ATime, ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY Atime) rn
     FROM
        AllTimes
), Intervals as (
    SELECT
       p1.PersonId,
       o1.ATime as StartTime,
       o2.ATime as EndTime
    FROM
       Ordered o1
          inner join
       Ordered o2
          on
              o1.PersonId = o2.PersonId and
              o1.rn = o2.rn - 1
)
SELECT
    * --TODO - Columns
FROM
   Intervals i
      inner join
   Person p
      on
          i.PersonId = p.PersonId and
          i.StartTime < p.SysEndTime and
          p.SysStartTime < i.EndTime
      inner join
   Email e
      on
          i.PersonId = e.PersonId and
          i.StartTime < e.SysEndTime and
          e.SysStartTime < i.EndTime
      inner join
   PhoneNumber pn
      on
          i.PersonId = pn.PersonId and
          i.StartTime < pn.SysEndTime and
          pn.SysStartTime < i.EndTime

如果您只想要一个人的详细信息,使用适当的过滤器,优化器有望解决。对于我错过的连接,可能还有其他过滤器。

希望您能看到 3 个 CTE 如何构建时间线。我们利用UNION 消除第一个重复项。

【讨论】:

我试过了(需要将 p1.PersonId 更改为 o1.PersonId),否则查询会运行。但是它不执行任何时间查询,所以我只获得任何记录上的最新版本。

以上是关于视图中的系统版本化(临时)表的主要内容,如果未能解决你的问题,请参考以下文章

删除系统版本化时态表的过程

在 Entity Framework Core 中查询系统版本时态表中的数据

使用视图和临时表之间有啥区别(系统资源方面)?

调整SQL Server中的大型查询

避免系统版本表中的架构不匹配

Tempdb总结