【问题标题】:Temporal Table : specific column changed version details only临时表:特定列仅更改版本详细信息
【发布时间】:2023-03-09 07:10:01
【问题描述】:

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

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

【问题讨论】:

  • 对此更清楚:如果该列也已更改,您只想获取更改的行(包含所有列)?或者您只想更改该列中的值?如果是后者,您是否需要其他列中的更改值?
  • @Charlieface 我希望仅在更新 1 个特定列时来自临时表的所有行。假设“Orderstatus”是我们正在寻找的列名,并且每当它们在“Orderstatus”上发生更新时,查询将从时态表中返回版本详细信息(所有行),其中仅“Orderstatus”列得到更新。当“orderstatus”未更新且其他列被修改时,我们希望排除这些行。
  • 你还想要所有的列,还是只想要那一列?您需要新值还是旧值?
  • 是的,也需要所有列。要求是跟踪谁更新了此列,以及当此列被修订时更新了哪些其他更改。
  • 您同时标记了 mysqlsql-server。是哪个?

标签: sql sql-server temporal-tables


【解决方案1】:

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

[re-]创建代表表

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 OrderStatus UpdatedBy ValidFrom ValidTo
9 NEW George 2021-02-17 10:27:35.1632525 2021-02-17 10:27:37.1719903
9 IN PROGRESS George 2021-02-17 10:27:37.1719903 2021-02-17 10:27:39.1852032
9 DESPATCHED George 2021-02-17 10:27:39.1852032 9999-12-31 23:59:59.9999999
46 NEW George 2021-02-17 10:27:35.1632525 2021-02-17 10:27:39.1852032
46 DESPATCHED George 2021-02-17 10:27:39.1852032 2021-02-17 10:27:41.1995704
46 IN PROGRESS George 2021-02-17 10:27:41.1995704 2021-02-17 10:27:43.2171042
46 IN PROGRESS Not George 2021-02-17 10:27:43.2171042 2021-02-17 10:27:45.2328908
46 DESPATCHED Still Not George 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 OrderStatus UpdatedBy ValidFrom
9 NEW George 2021-02-17 10:27:35.1632525
9 IN PROGRESS George 2021-02-17 10:27:37.1719903
9 DESPATCHED George 2021-02-17 10:27:39.1852032
46 NEW George 2021-02-17 10:27:35.1632525
46 DESPATCHED George 2021-02-17 10:27:39.1852032
46 IN PROGRESS Not George 2021-02-17 10:27:43.2171042
46 DESPATCHED Still Not George 2021-02-17 10:27:45.2328908

【讨论】:

    【解决方案2】:

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

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

    在这种情况下,我使用分析器的 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'
    

    【讨论】:

      【解决方案3】:

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

      那么我们需要做的是首先查询这些日期之间的所有rowversions,然后使用LAG/LEAD检查该行是否发生了变化。

      问题:如果我们要求给定日期,我们不会得到之前的版本。为此,我们需要再次查询该表。在主查询中使用BETWEEN(包括)而不是FROM(不包括)会使这变得更加困难,因为我们必须找到一种方法来获取行&lt; @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;
      

      【讨论】:

      • 感谢分享查询但无法执行。你能帮我提供 start_lsn,end_lsn & ordinal 需要声明的内容吗?
      • 已更新。但是你真的需要了解它是如何工作的,它非常复杂,我还没有测试过这段代码
      • 他们使用的是临时表,而不是更改数据捕获
      猜你喜欢
      • 2017-03-06
      • 2011-03-27
      • 2011-03-24
      • 1970-01-01
      • 1970-01-01
      • 2015-08-23
      • 2015-01-31
      • 2015-11-02
      • 2015-01-07
      相关资源
      最近更新 更多