如何解决临时表:特定列仅更改版本详细信息 原始数据结果
需要有关查询时态表的帮助/想法。我在表上启用了 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 举报,一经查实,本站将立刻删除。