我想知道何时UserId
更改为当前值。
说我们有一张桌子Foo
:
Foo
Id | UserId
---+-------
1 | 1
2 | 2
现在,我需要能够执行如下查询:
SELECT UserId, UserIdModifiedAt FROM Foo
幸运的是,我已将历史记录中的所有更改记录到表FooHistory
:
FooHistory
Id | FooId | UserId | FooModifiedAt
---|-------+--------+---------------
1 | 1 | NULL | 1.1.2019 02:00
2 | 1 | 2 | 1.1.2019 02:01
3 | 1 | 1 | 1.1.2019 02:02
4 | 1 | 1 | 1.1.2019 02:03
5 | 2 | 1 | 1.1.2019 02:04
6 | 2 | 2 | 1.1.2019 02:05
7 | 2 | 2 | 1.1.2019 02:06
因此我们需要的所有数据都可用(在Foo#1的用户上次修改时间为02:02和Foo#2的用户上次修改时间为02:05)。我们将在UserIdModifiedAt
Foo
Foo v2
Id | UserId | UserIdModifiedAt
---+--------|-----------------
1 | 1 | NULL
2 | 2 | NULL
...并使用触发器设置其值。精细。但是如何迁移历史记录?哪种脚本可以为我们填充UserIdModifiedAt
?
查看表结构示例:
DROP TABLE IF EXISTS [Foo]
DROP TABLE IF EXISTS [FooHistory]
CREATE TABLE [Foo]
(
[Id] INT NOT NULL CONSTRAINT [PK_Foo] PRIMARY KEY,
[UserId] INT,
[UserIdModifiedAt] DATETIME2 -- Automatically updated based on a trigger
)
CREATE TABLE [FooHistory]
(
[Id] INT IDENTITY NOT NULL CONSTRAINT [PK_FooHistory] PRIMARY KEY,
[FooId] INT,
[UserId] INT,
[FooModifiedAt] DATETIME2 NOT NULL CONSTRAINT [DF_FooHistory_FooModifiedAt] DEFAULT (sysutcdatetime())
)
GO
CREATE TRIGGER [trgFoo]
ON [dbo].[Foo]
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (SELECT [UserId] FROM inserted EXCEPT SELECT [UserId] FROM deleted)
BEGIN
UPDATE [Foo] SET [UserIdModifiedAt] = SYSUTCDATETIME() FROM [inserted] WHERE [Foo].[Id] = [inserted].[Id]
END
INSERT INTO [FooHistory] ([FooId], [UserId])
SELECT [Id], [UserId] FROM inserted
END
GO
/* Test data */
INSERT INTO [Foo] ([Id], [UserId]) VALUES (1, NULL)
WAITFOR DELAY '00:00:00.010'
UPDATE [Foo] SET [UserId] = NULL
WAITFOR DELAY '00:00:00.010'
UPDATE [Foo] SET [UserId] = 1
WAITFOR DELAY '00:00:00.010'
UPDATE [Foo] SET [UserId] = 1
WAITFOR DELAY '00:00:00.010'
SELECT * FROM [Foo]
SELECT * FROM [FooHistory]
答案 0 :(得分:1)
如果我正确理解您的问题,则似乎您已经通过在dbo.Foo上创建触发器的方式自己回答了问题。
看起来UserIdModifiedAt第一次被修改,而UserIdModifiedAt却没有改变,在这种情况下,您的答案就是dbo.Foo.UserIdModifiedAt。
如果您不打算像这样编写触发器,我认为可以从FooHistory中检索该值,但它要复杂得多。
下面的代码可能会满足我的要求
;WITH FooHistoryRanked
AS (
SELECT FH.Id, FH.FooId, FH.FooModifiedAt, FH.UserId
, RankedASC = ROW_NUMBER() OVER(PARTITION BY FH.FooId ORDER BY FooModifiedAt ASC) -- 1 = first change to that Foo record
FROM [FooHistory] FH
)
,Matches AS
(
SELECT FHR1.*
, PreviousUserId = FHR2.UserId
, PreviousFooModifiedAt = FHR2.FooModifiedAt
, PreviousHistoryId = FHR2.Id
FROM FooHistoryRanked FHR1
-- join on Foo filters on current value
INNER JOIN [Foo] F ON F.Id = FHR1.FooId
AND ( FHR1.UserId = F.UserId
OR (FHR1.UserId IS NULL AND F.UserId IS NULL)
)
-- Find preceding changes to a different value
LEFT JOIN FooHistoryRanked FHR2 ON FHR2.FooId = FHR1.FooId
AND FHR2.RankedASC = FHR1.RankedASC - 1 -- previous change
AND ( FHR2.UserId <> FHR1.UserId
OR ( FHR2.UserId IS NULL AND FHR1.UserId IS NOT NULL )
OR ( FHR2.UserId IS NOT NULL AND FHR1.UserId IS NULL )
)
)
,MatchesRanked AS
(
-- select the modifications that had a different value before OR that are the only modification
SELECT *, MatchRanked = ROW_NUMBER() OVER(PARTITION BY FooId ORDER BY Id DESC)
FROM Matches
WHERE RankedASC = 1 OR PreviousFooModifiedAt IS NOT NULL
)
SELECT *
FROM MatchesRanked
WHERE MatchRanked = 1 -- just get the last qualifying record
ORDER BY FooId, FooModifiedAt DESC, UserId;
PS: 1)如果这些表很大,性能可能会成为问题... 2)您可能可以使用LAG代替LEFT JOIN,但是我只是习惯用这种方式做事...