簡體   English   中英

SQL Server 2016 - 時態表 - 如何識別用戶

[英]SQL Server 2016 - Temporal Table - how to identify the user

是否可以獲取有關修改歷史表中數據的用戶/連接的信息? 我閱讀了有關可以使用時態表的審計方案,並且可以檢測到誰更改了數據。 但是我該怎么做呢?

一個看似無懈可擊的審計解決方案,它給出了進行每次更改的登錄用戶的名稱(以及我之前在此頁面上的回答的巨大改進):

SELECT 
  e.EmployeeID, e.FirstName, e.Score, 
  COALESCE (eh.LoggedInUser, o.CreatedBy, e.CreatedBy) AS CreatedOrModifiedBy, 
  e.ValidFromUTC, e.ValidToUTC

FROM dbo.Employees FOR SYSTEM_TIME ALL AS e
  LEFT JOIN dbo.EmployeeHistory AS eh    -- history table
    ON e.EmployeeID = eh.EmployeeID AND e.ValidFromUTC = eh.ValidToUTC
    AND e.ValidFromUTC <> eh.ValidFromUTC

OUTER APPLY
  (SELECT TOP 1 CreatedBy 
   FROM dbo.EmployeeHistory 
   WHERE EmployeeID = e.EmployeeID 
   ORDER BY ValidFromUTC ASC) AS o     -- oldest history record

--WHERE e.EmployeeID = 1
ORDER BY e.ValidFromUTC

結果集

  • 不使用觸發器或用戶定義的函數
  • 需要對表進行小的更改
  • 注意:請注意,SQL Server 始終使用UTC而不是本地時間作為時態表中的時間戳。
  • 編輯: (2018/12/03)(感謝@JussiKosunen!)當同一條記錄同時發生多個更新時(例如在一個事務中),只返回最新的更改(見下文)

解釋:

主表和歷史表中添加了兩個字段:

  • 記錄創建記錄的用戶的姓名 - 一個普通的 SQL 默認值:
    CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME())
  • 隨時記錄當前登錄用戶的姓名。 一個計算列:
    LoggedInUser AS (SUSER_SNAME())

當一條記錄插入到主表中時,SQL Server 不會向歷史表中插入任何內容。 但是字段CreatedBy記錄誰創建了記錄,因為默認約束。 但是,如果/當記錄更新時,SQL Server 會在關聯的歷史記錄表中插入一條記錄。 這里的關鍵思想是將進行更改的登錄用戶名稱記錄到歷史表中,即主表​​中字段LoggedInUser的內容(它始終包含登錄到連接的用戶的名稱)保存到歷史表中的LoggedInUser字段中。

這幾乎是我們想要的,但不完全是——這是一個變化。 例如,如果用戶 Dave 插入了記錄,但用戶 Andrew 進行了第一次更新,則“Andrew”作為用戶名記錄在歷史表中,緊挨着 Dave 插入的記錄的原始內容。 然而,所有的信息都在那里——它只需要被解開。 加入系統為 ROW START 和 ROW END 生成的字段,我們得到進行更改的用戶(來自歷史表中的前一條記錄)。 但是,歷史記錄表中沒有記錄的原始插入版本的記錄。 在這種情況下,我們檢索CreatedBy字段。

這似乎提供了一個無懈可擊的審計解決方案。 即使用戶編輯了CreatedBy字段,該編輯也會記錄在歷史表中。 出於這個原因,我們從歷史表中恢復CreatedBy的最舊值,而不是從主表中恢復當前值。

刪除的記錄

上面的查詢沒有顯示誰從主表中刪除了記錄。 這可以使用以下方法檢索(可以簡化嗎?):

SELECT 
  d.EmployeeID, d.LoggedInUser AS DeletedBy, 
  d.CreatedBy, d.ValidFromUTC, d.ValidToUTC AS DeletedAtUTC
FROM
  (SELECT EmployeeID FROM dbo.EmployeeHistory GROUP BY EmployeeID) AS eh   -- list of IDs
OUTER APPLY
  (SELECT TOP 1 * FROM dbo.EmployeeHistory 
   WHERE EmployeeID = eh.EmployeeID 
   ORDER BY ValidToUTC DESC) AS d -- last history record, which may be for DELETE
LEFT JOIN
  dbo.Employees AS e
    ON eh.EmployeeID = e.EmployeeID
WHERE e.EmployeeID IS NULL          -- record is no longer in main table

示例表腳本

以上示例基於表腳本(歷史表由SQL Server創建):

CREATE TABLE dbo.Employees(
  EmployeeID INT /*IDENTITY(1,1)*/ NOT NULL,
  FirstName NVARCHAR(40) NOT NULL,
  Score INTEGER NULL,
  LoggedInUser AS (SUSER_SNAME()),
  CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME()),
  ValidFromUTC DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL DEFAULT SYSUTCDATETIME(),
  ValidToUTC DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2),
  CONSTRAINT PK_Employees PRIMARY KEY CLUSTERED (EmployeeID ASC),
  PERIOD FOR SYSTEM_TIME (ValidFromUTC, ValidToUTC)
) 
WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ))

編輯: (2018 年 11 月 19 日)添加了針對 system_time 字段的默認約束,一些人認為這是最佳實踐,如果您將系統版本控制添加到現有表中會有所幫助。

編輯: (2018/12/03)根據@JussiKosunen 的評論更新(感謝 Jussi!)。 請注意,當多個更改具有相同的時間戳時,查詢僅返回當時的最后一個更改。 以前它為每個更改返回一行,但每個都包含最后一個值。 尋找一種方法使其返回所有更改,即使它們具有相同的時間戳。 (請注意,這是一個真實世界的時間戳,而不是為了避免破壞物理世界而被棄用的“ Microsoft 時間戳”。)

編輯: (2019/03/22)修復了查詢中顯示已刪除記錄的錯誤,在某些情況下它會返回錯誤的記錄。

編輯:在本頁的其他地方看到我更好的答案

我的解決方案不需要觸發器。 我在主表中有一個計算列,它總是包含登錄用戶,例如

CREATE TABLE dbo.Employees(
    EmployeeID INT NOT NULL,
    FirstName sysname NOT NULL,
    ValidFrom DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL,
    LoggedInUser AS (SUSER_SNAME()),  --<<-- computed column

... 等等。

LoggedInUser字段始終包含每個記錄中當前登錄用戶的名稱,因此對任何記錄進行任何更改時保存到歷史表

當然,這在主表中不是很有用,因為它沒有顯示誰對每條記錄進行了最后一次更改。 但是在歷史記錄表中,它在進行更改時被凍結,這非常有用,(盡管它在周期結束時記錄用戶,而不是開始時)。

注意,作為計算列, LoggedInUser必須可以為空,因此歷史表中對應的列也必須可以為空。

主(當前)表: 主桌

歷史表: 歷史表

當然,在歷史表,它記錄誰該狀態改變的記錄,還沒那個狀態,即登錄的用戶在有效期結束。 它也適用於刪除,但 SQL Server 時態表系統不會在歷史表中插入記錄以進行插入。

歡迎任何有關如何改進這一點的想法,例如,如何在歷史表中的每個有效期開始時記錄誰進行了更改。 我有一個想法,涉及主表中的另一個計算字段,它使用 UDF 來獲取在歷史記錄表中進行最后更改的用戶。

編輯:我從@Aaron Bertrand 在這里使用觸發器的優秀文章中找到了很多靈感。

在時態表的當前實現中,它只記錄基於時間的信息,而沒有關於進行更改的會話的其他信息。 不要因為我知道這種情況在未來可能會發生變化而閱讀該聲明; 我對此一無所知。 如果您需要該信息,則需要將其記錄在一行中。 執行此操作的經典方法是使用觸發 DML 操作並代表用戶維護該值的觸發器。

我正在考慮解決此問題的另一個選項是在您的基本時態表中有一個 LastModifiedBy 字段,該字段在保存或更新行時填充。

這將顯示誰修改了表並因此創建了歷史記錄。 正如 Aaron 上面提到的,你可以在觸發器中完成它,但我的想法是在它到達插入/更新之前決定它並在更新記錄時將值放在 LastModifiedBy 字段中。

每次修改記錄時,這也將在歷史表中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM