【问题标题】:Migrate time when column was last changed from history table?上次从历史表中更改列的迁移时间?
【发布时间】:2019-02-25 00:30:17
【问题描述】:

我想知道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)。我们将在Foo 中添加一个新列UserIdModifiedAt

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?

【问题讨论】:

  • 为什么不使用临时表?
  • @ArnaudPeralta 澄清了这个问题。我们需要快速执行查询 SELECT UserId, UserIdModifiedAt FROM Foo。请注意,UserIdModifiedAt 具体与 UserId 值相关。我认为临时表在这里对我们没有帮助;在跟踪行何时被修改时,它不会向我们提供有关特定值上次修改时间的信息?

标签: sql-server tsql


【解决方案1】:

如果我正确理解您的问题,您似乎已经通过在 dbo.Foo 上创建触发器的方式自己回答了问题。

看起来 UserIdModifiedAt 在 UserId 第一次更改时被修改,而在没有更改时未修改,在这种情况下,您的答案只是 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,但我只是习惯于这样做......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    • 2020-07-04
    • 1970-01-01
    相关资源
    最近更新 更多