临时表:特定列仅更改版本详细信息

时间:2021-02-15 04:54:38

标签: sql sql-server temporal-tables

需要有关查询时态表的帮助/想法。我在表上启用了 SQL 版本控制。该表目前有 15 列。

确切的要求是确定“OrderStatus”列更新了多少次以及谁在什么时间更新了它?我们只想查看“OrderStatus”列在特定日期与默认选择的所有其他列之间更新的次数。

3 个答案:

答案 0 :(得分:2)

你的问题缺少一些细节,所以我根据一些假设进行了尝试

[重新]创建代表表

IF Object_ID('dbo.orders', 'U') IS NOT NULL
  BEGIN
    ALTER TABLE dbo.orders SET (SYSTEM_VERSIONING = OFF);
  END
;
DROP TABLE IF EXISTS dbo.orders_history;
DROP TABLE IF EXISTS dbo.orders;

CREATE TABLE dbo.orders (
   OrderId     int         NOT NULL IDENTITY(9,37)
 , OrderStatus varchar(20) NOT NULL
 , UpdatedBy   varchar(20) NOT NULL
 , ValidFrom   datetime2 GENERATED ALWAYS AS ROW START
 , ValidTo     datetime2 GENERATED ALWAYS AS ROW END
 , PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
 , CONSTRAINT pk_dbo_orders PRIMARY KEY (OrderId)
)
WITH (
  SYSTEM_VERSIONING = ON (
    HISTORY_TABLE = dbo.orders_history
  )
);

创建示例数据

请注意,WAITFOR 的使用是为了在事件之间提供一些更具说明性的差距。

INSERT INTO dbo.orders (OrderStatus, UpdatedBy)
  VALUES ('NEW', 'George')
       , ('NEW', 'George')
;
WAITFOR DELAY '00:00:02';

UPDATE dbo.orders
SET    OrderStatus = 'IN PROGRESS'
WHERE  OrderId = 9
;
WAITFOR DELAY '00:00:02';

-- Mark both orders as despatched
UPDATE dbo.orders
SET    OrderStatus = 'DESPATCHED'
;
WAITFOR DELAY '00:00:02';

-- Whoops, order #46 wasn't supposed to be marked as dispatched
UPDATE dbo.orders
SET    OrderStatus = 'IN PROGRESS'
WHERE  OrderId = 46
;
WAITFOR DELAY '00:00:02';

-- Mark it as in progress again, but changing the person who did the operation
UPDATE dbo.orders
SET    OrderStatus = 'IN PROGRESS'
     , UpdatedBy   = 'Not George'
WHERE  OrderId = 46
;
WAITFOR DELAY '00:00:02';

-- _Now_ it is despatched
UPDATE dbo.orders
SET    OrderStatus = 'DESPATCHED'
     , UpdatedBy   = 'George'
WHERE  OrderId = 46
;

原始数据

让我们看看原始数据

SELECT OrderId
     , OrderStatus
     , UpdatedBy
     , ValidFrom
     , ValidTo
FROM   dbo.orders FOR SYSTEM_TIME ALL
ORDER
    BY OrderId
     , ValidFrom
;
<头>
OrderId 订单状态 UpdatedBy ValidFrom ValidTo
9 乔治 2021-02-17 10:27:35.1632525 2021-02-17 10:27:37.1719903
9 进行中 乔治 2021-02-17 10:27:37.1719903 2021-02-17 10:27:39.1852032
9 已发送 乔治 2021-02-17 10:27:39.1852032 9999-12-31 23:59:59.9999999
46 乔治 2021-02-17 10:27:35.1632525 2021-02-17 10:27:39.1852032
46 已发送 乔治 2021-02-17 10:27:39.1852032 2021-02-17 10:27:41.1995704
46 进行中 乔治 2021-02-17 10:27:41.1995704 2021-02-17 10:27:43.2171042
46 进行中 不是乔治 2021-02-17 10:27:43.2171042 2021-02-17 10:27:45.2328908
46 已发送 仍然不是乔治 2021-02-17 10:27:45.2328908 9999-12-31 23:59:59.9999999

查询时间

; WITH _orders AS (
  SELECT OrderId
       , OrderStatus
       , UpdatedBy
       , ValidFrom
       , ValidTo
       , Lead(OrderStatus) OVER (PARTITION BY OrderId ORDER BY ValidFrom) AS NextOrderStatus
  FROM   dbo.orders FOR SYSTEM_TIME ALL
)
SELECT OrderId
     , OrderStatus
     , UpdatedBy
     , ValidFrom
FROM   _orders
WHERE  OrderStatus <> NextOrderStatus -- Only return records where the order status has changed
OR     NextOrderStatus IS NULL -- Include the "most recent" record in the results, always.
ORDER
    BY OrderId
     , ValidFrom
;

结果

<头>
OrderId 订单状态 UpdatedBy ValidFrom
9 乔治 2021-02-17 10:27:35.1632525
9 进行中 乔治 2021-02-17 10:27:37.1719903
9 已发送 乔治 2021-02-17 10:27:39.1852032
46 乔治 2021-02-17 10:27:35.1632525
46 已发送 乔治 2021-02-17 10:27:39.1852032
46 进行中 不是乔治 2021-02-17 10:27:43.2171042
46 已发送 仍然不是乔治 2021-02-17 10:27:45.2328908

答案 1 :(得分:0)

您尝试做的问题是历史记录表没有说明哪些列已更新。

然后我们需要做的是,首先查询这些日期之间的所有行版本,然后使用 LAG/LEAD 检查该行是否已更改。

问题:如果我们要求提供给定的日期,我们将无法获得在那之前的版本。为此,我们需要再次查询该表。在主查询上使用 BETWEEN(包含)而不是 FROM(不包含)会使这变得更加困难,因为我们必须找到一种方法来获取行 < @start

SELECT *
FROM (
    SELECT *,
        ROW_NUMBER() OVER (PARTITION BY pkID ORDER BY SysStartTime) rn,   -- or whatever your startTime column is called
        LAG(OrderStatus) OVER (PARTITION BY pkID ORDER BY SysStartTime) PrevStatus
    FROM myTable t
    FOR SYSTEM_TIME FROM @start TO @end    -- FROM is strictly exclusive
) t

WHERE PrevStatus <> OrderStatus OR
    (rn = 1 AND EXISTS (SELECT 1
        FROM myTable t2
        FOR SYSTEM_TIME AT @start
        WHERE t2.ID = t.ID
            AND t2.OrderStatus <> t.OrderStatus
    ))

更改数据捕获

使用 CDC,设置查询有点复杂,但我想它会表现得更好。

您通常会使用 fn_cdc_get_column_ordinal 来获取列号,然后在 WHERE 中使用它来过滤更新掩码。

您还需要将最近收到的 @lsn 作为 binary(10) 传递(如果从新鲜开始,则为空 binary(10))。您会在下面的第一个结果集中收到新的结果。

DECLARE @from_lsn binary(10) = sys.fn_cdc_get_min_lsn('myTable');  -- get the new low mark
DECLARE @to_lsn binary(10) = sys.fn_cdc_get_max_lsn();  --get the new high mark
SELECT @to_lsn;    -- send back the new high, which becomes the low on the next run

SET @lsn = sys.fn_cdc_increment_lsn (@lsn);   -- get next LSN after the old high
IF (sys.fn_cdc_get_min_lsn (N'myTable') > @lsn)
    SELECT * FROM myTable;       -- need to do a full refresh
ELSE
BEGIN
    DECLARE @ordinal int = sys.fn_cdc_get_column_ordinal (N'myTable', N'myCol');
    SELECT *
    FROM sys.fn_cdc_get_all_changes_capture_myTable
        (@from_lsn, @to_lsn, 'all')
    WHERE sys.fn_cdc_is_bit_set (__$update_mask, @ordinal) = 1;
END;

答案 2 :(得分:0)

版本控制无法为您提供此信息,因为在“为相同值更新”的情况下,将创建一个版本化行,但您将永远无法看到修改的是该列还是其他列。

唯一的方法是在表中创建一个触发器并在某个对象中计数。

在这个 cas 中,我使用了分析器的 10 个用户可配置计数器之一... 以触​​发器的代码为例:

CREATE TRIGGER E_U_MY_TABLE
ON dbo.MY_TABLE
FOR UPDATE
AS
SET NOCOUNT 1;
IF NOT UPDATE(OrderStatus)
   RETURN;
DECLARE @COUNT BIGINT = (SELECT cntr_value 
                         FROM   sys.dm_os_performance_counters 
                         WHERE instance_name = 'User counter 1') + 1;
EXEC sp_user_counter1 @COUNT;
GO

您可以随时阅读:

SELECT cntr_value 
FROM   sys.dm_os_performance_counters 
WHERE instance_name = 'User counter 1'