Sql server SQL Server 2016-时态表-如何识别用户

Sql server SQL Server 2016-时态表-如何识别用户,sql-server,sql-server-2016,Sql Server,Sql Server 2016,是否可以获取有关修改历史表中数据的用户/连接的信息?我读过关于审计场景的文章,在那里我可以使用时态表,并且可以检测谁更改了数据。但是我如何才能做到这一点呢?在时态表的当前实现中,它只记录基于时间的信息,而不记录做出更改的会话的其他信息。不要把这句话当作我有某种内幕知识,知道这种情况将来可能会改变;我对此一无所知。如果您需要该信息,则需要将其记录在行中。实现这一点的经典方法是使用触发DML操作并代表用户维护该值的触发器。我正在考虑解决此问题的另一个选项是在基本时态表中有一个LastModified

是否可以获取有关修改历史表中数据的用户/连接的信息?我读过关于审计场景的文章,在那里我可以使用时态表,并且可以检测谁更改了数据。但是我如何才能做到这一点呢?

在时态表的当前实现中,它只记录基于时间的信息,而不记录做出更改的会话的其他信息。不要把这句话当作我有某种内幕知识,知道这种情况将来可能会改变;我对此一无所知。如果您需要该信息,则需要将其记录在行中。实现这一点的经典方法是使用触发DML操作并代表用户维护该值的触发器。

我正在考虑解决此问题的另一个选项是在基本时态表中有一个LastModifiedBy字段,该字段在保存或更新行时填充

这将显示谁修改了表,从而创建了历史记录。正如Aaron在上面提到的,您可以在触发器中执行,但我的想法是在插入/更新之前确定它,并在更新记录时将值放入LastModifiedBy字段


每次修改记录时,记录也会出现在历史记录表中。

编辑:请参阅本页其他地方的“我的记录”

我的解决方案不需要触发器。我在主表中有一个计算列,它始终包含登录的用户,例如

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
创建表dbo.Employees(
EmployeeID INT不为空,
FirstName sysname不为空,
ValidFrom DATETIME2(7)始终生成为行开始非空,
ValidTo DATETIME2(7)始终作为行结束非空生成,

LoggedInUser作为(SUSER_SNAME()),--一个看似无懈可击的审计解决方案,它给出了每次更改的登录用户的姓名(这是我在本页上的一个重大改进):

示例表脚本 以上示例基于表脚本(历史记录表由SQL Server创建):

编辑:(2018/11/19)添加了针对系统时间字段的默认约束,有些人认为这是最佳做法,如果要将系统版本控制添加到现有表中,这会有所帮助

编辑:(2018/12/03)根据@JussiKosunen的评论更新(谢谢Jussi!)。请注意,当多个更改具有相同的时间戳时,查询仅返回当时的最后一个更改。以前,它为每个更改返回一行,但每个都包含最后的值。正在寻找一种方法,使其返回所有更改,即使它们具有相同的时间戳。(请注意,这是一个真实的时间戳,而不是“”这是为了避免腐蚀物质世界。)


编辑:(2019/03/22)修复了查询中显示已删除记录的错误,在某些情况下,该错误会返回错误记录。

这不是真正用于审核的错误。仔细想想,当用户修改行时,基表中的行就是用户更改的行。因此,您无法捕获导致历史记录行为crea的人的用户名ted,他们可能正在更新我昨天更新的行,因此移动到历史记录表的行表示我昨天所做的更改。因此,您需要在基表中有一列使用触发器进行更新,但这将创建两个版本的历史记录行(一个是前一个编辑器,一个是新编辑器)。不,你不能用INSTEAD OF触发器来规避这个问题。我试过了,有一个漏洞,但是.Thx提示。我读了这篇德语/英语msdn文章数据审核“对存储关键信息的表使用临时系统版本控制,您需要跟踪这些信息的更改内容、更改时间和更改人,并在任何时间点执行数据取证。“我目前使用的是一个带有usercolumn和cdc的解决方案。我想我可以用一个时态表来代替这个解决方案。但看起来我必须等待下一次更新。是的,那篇文章确实提到了审计数据,但这不是我所说的审计。除了一个挥手的“谁”之外它没有提到任何关于审核做出更改的用户的明确内容。文档现在已经更新了-请注意,如果他们可以通过存储过程控制所有数据访问和/或访问发布更新的应用程序代码,这将是可行的。许多人不能。我做了一个(尚未准备好生产)使用LastModifiedBy字段并利用新会话上下文功能的解决方案。我使用值
SESSION\u CONTEXT('UserID')在该字段上创建了一个默认约束
我在打开连接时设置的。它可以正常工作,但如果有人明确说明该值,它可以被覆盖。Thx用于共享您的解决方案,这对我非常有用。小问题:查询在事务中多次更新同一行后显示重复项,因为它们在历史记录表中的时间相同。这可能会导致e通过执行前1个应用程序而不是左连接来解决(尽管更昂贵)。作为我之前评论的补充,或者将
和eh.validfromtc!=eh.validfromtc
添加到左侧联接,以确保只返回一个值。感谢@JussiKosunen收集到该值!我猜您的意思是
和e.validfromtc!=eh.validfromtc
e
不是
eh
)。我已经更新了答案。这似乎也有效。我的答案确实有输入错误,但我的意思是
和eh.validfromtc!=eh.ValidToUTC
(两者都来自
eh
,但
from
不是
),因为这似乎是SQL Server在所有系统时间使用
来防止重复时所做的,这正是我本应该尝试的(我的意思是,如果我们走得这么远,必须是一种没有触发器的方式)。谢谢你,我甚至没有机会
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
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
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 ))