微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

临时表:特定列仅更改版本详细信息 原始数据结果

如何解决临时表:特定列仅更改版本详细信息 原始数据结果

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

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

解决方法

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

[重新]创建代表表

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,ValidTo,Lead(OrderStatus) OVER (PARTITION BY OrderId ORDER BY ValidFrom) AS NextOrderStatus
  FROM   dbo.orders FOR SYSTEM_TIME ALL
)
SELECT OrderId,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
,

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

然后我们需要做的是,首先查询这些日期之间的所有行版本,然后使用 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;
,

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

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

在这个 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'

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。