迁移历史记录表中列的最后更改时间?

时间:2019-02-23 10:31:42

标签: sql-server tsql

我想知道何时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]

相关问题:Select first row in each GROUP BY group?

1 个答案:

答案 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,但是我只是习惯用这种方式做事...