简体   繁体   English

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

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

Is it possible to get the information about the user/connection that modified data that is in the historical table?是否可以获取有关修改历史表中数据的用户/连接的信息? I read about the audit scenario where I can use temporal tables and that it's possible to detect who has changed the data.我阅读了有关可以使用时态表的审计方案,并且可以检测到谁更改了数据。 But how can I do that?但是我该怎么做呢?

A seemingly watertight auditing solution, which gives the name of the logged-in user who made each change (and a great improvement on my previous answer on this page):一个看似无懈可击的审计解决方案,它给出了进行每次更改的登录用户的名称(以及我之前在此页面上的回答的巨大改进):

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

结果集

  • Does not use triggers or user defined functions不使用触发器或用户定义的函数
  • Requires small changes to the table需要对表进行小的更改
  • NB: Note that SQL Server always uses UTC , not local time, for time stamps in temporal tables.注意:请注意,SQL Server 始终使用UTC而不是本地时间作为时态表中的时间戳。
  • Edit: (2018/12/03) (Thanks @JussiKosunen!) When multiple updates occur on the same record at the same time (eg in a transaction), only the latest change is returned (see below)编辑: (2018/12/03)(感谢@JussiKosunen!)当同一条记录同时发生多个更新时(例如在一个事务中),只返回最新的更改(见下文)

Explanation:解释:

Two fields are added to the main and history tables:主表和历史表中添加了两个字段:

  • To record the name of the user who created the record - a normal SQL default:记录创建记录的用户的姓名 - 一个普通的 SQL 默认值:
    CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME())
  • To record the name of the current logged in user at any time.随时记录当前登录用户的姓名。 A computed column:一个计算列:
    LoggedInUser AS (SUSER_SNAME())

When a record is inserted into the main table, SQL Server does not insert anything into the history table.当一条记录插入到主表中时,SQL Server 不会向历史表中插入任何内容。 But the field CreatedBy records who created the record, because of the default constraint.但是字段CreatedBy记录谁创建了记录,因为默认约束。 But if/when the record gets updated, SQL Server inserts a record into the associated history table.但是,如果/当记录更新时,SQL Server 会在关联的历史记录表中插入一条记录。 The key idea here is that the name of the logged-in user who made the change is recorded into the history table , ie the contents of field LoggedInUser in the main table (which always contains the name of who is logged in to the connection) is saved into the field LoggedInUser in the history table.这里的关键思想是将进行更改的登录用户名称记录到历史表中,即主表​​中字段LoggedInUser的内容(它始终包含登录到连接的用户的名称)保存到历史表中的LoggedInUser字段中。

That's almost what we want, but not quite - it's one change behind.这几乎是我们想要的,但不完全是——这是一个变化。 Eg if user Dave inserted the record, but user Andrew made the first update, "Andrew" is recorded as the user name in the history table, next to the original contents of the record that Dave inserted.例如,如果用户 Dave 插入了记录,但用户 Andrew 进行了第一次更新,则“Andrew”作为用户名记录在历史表中,紧挨着 Dave 插入的记录的原始内容。 However, all the information is there - it just needs to be unravelled.然而,所有的信息都在那里——它只需要被解开。 Joining the system generated fields for ROW START and ROW END, we get the user who made the change (from the previous record in the history table).加入系统为 ROW START 和 ROW END 生成的字段,我们得到进行更改的用户(来自历史表中的前一条记录)。 However, there's no record in the history table for the originally inserted version of the record.但是,历史记录表中没有记录的原始插入版本的记录。 In that case we retrieve the CreatedBy field.在这种情况下,我们检索CreatedBy字段。

This seems to provide a watertight auditing solution.这似乎提供了一个无懈可击的审计解决方案。 Even if a user edits the field CreatedBy , the edit will be recorded in the history table.即使用户编辑了CreatedBy字段,该编辑也会记录在历史表中。 For that reason, we recover the oldest value for CreatedBy from the history table, instead of the current value from the main table.出于这个原因,我们从历史表中恢复CreatedBy的最旧值,而不是从主表中恢复当前值。

Deleted records删除的记录

The query above does not show who deleted records from the main table.上面的查询没有显示谁从主表中删除了记录。 This can be retrieved using the following (could be simplified?):这可以使用以下方法检索(可以简化吗?):

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

Sample table script示例表脚本

The above examples are based on the table script (history table is created by SQL Server):以上示例基于表脚本(历史表由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 ))

Edit: (2018/11/19) Added default constraints against the system_time fields, which is considered by some to be best practice, and helps if you're adding system-versioning to an existing table.编辑: (2018 年 11 月 19 日)添加了针对 system_time 字段的默认约束,一些人认为这是最佳实践,如果您将系统版本控制添加到现有表中会有所帮助。

Edit: (2018/12/03) Updated as per @JussiKosunen's comment (Thanks Jussi!).编辑: (2018/12/03)根据@JussiKosunen 的评论更新(感谢 Jussi!)。 Note that when multiple changes have the same timestamp, then the query returns only the last change at that time.请注意,当多个更改具有相同的时间戳时,查询仅返回当时的最后一个更改。 Previously it returned a row for each change, but each containing the last values.以前它为每个更改返回一行,但每个都包含最后一个值。 Looking at a way to make it return all changes, even when they have the same timestamp.寻找一种方法使其返回所有更改,即使它们具有相同的时间戳。 (Note that's a real-world timestamp, not a " Microsoft timestamp " which is deprecated to avoid corrupting the physical universe.) (请注意,这是一个真实世界的时间戳,而不是为了避免破坏物理世界而被弃用的“ Microsoft 时间戳”。)

Edit: (2019/03/22) Fixed a bug in the query which shows deleted records, where under certain conditions it would return the wrong record.编辑: (2019/03/22)修复了查询中显示已删除记录的错误,在某些情况下它会返回错误的记录。

EDIT: See my much better answer elsewhere on this page编辑:在本页的其他地方看到我更好的答案

My solution does not need triggers.我的解决方案不需要触发器。 I have a computed column in the main table which always contains the logged in user, eg我在主表中有一个计算列,它总是包含登录用户,例如

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

... etc. ... 等等。

The field LoggedInUser always contains the name of the currently logged in user in every record , and is thus saved into the history table at the time any change was made to any record. LoggedInUser字段始终包含每个记录中当前登录用户的名称,因此对任何记录进行任何更改时保存到历史表

Of course, that's not very useful in the main table, as it doesn't show who made the last change for each record.当然,这在主表中不是很有用,因为它没有显示谁对每条记录进行了最后一次更改。 But in the history table it gets frozen at the point the change was made, which is very useful , (although it records the user at the end of the period, not the start).但是在历史记录表中,它在进行更改时被冻结,这非常有用,(尽管它在周期结束时记录用户,而不是开始时)。

Note that as a computed column, LoggedInUser must be nullable, and therefore the corresponding column in the history table must be as well.注意,作为计算列, LoggedInUser必须可以为空,因此历史表中对应的列也必须可以为空。

Main (current) table:主(当前)表: 主桌

History table:历史表: 历史表

Of course in the history table, it records who changed the record from that state, not to that state, ie the logged in user at the end of the validity period.当然,在历史表,它记录谁该状态改变的记录,还没那个状态,即登录的用户在有效期结束。 It works for deletes as well, but the SQL Server temporal table system does not insert a record in the history table for inserts.它也适用于删除,但 SQL Server 时态表系统不会在历史表中插入记录以进行插入。

Any ideas about how to improve this would be welcome, eg how to record who made the change at the start of each validity period in the history table.欢迎任何有关如何改进这一点的想法,例如,如何在历史表中的每个有效期开始时记录谁进行了更改。 I have an idea involving another calculated field in the main table, which uses a UDF to get the user who made the last change in the history table.我有一个想法,涉及主表中的另一个计算字段,它使用 UDF 来获取在历史记录表中进行最后更改的用户。

Edit: I found a lot of inspiration from @Aaron Bertrand's excellent article here , which uses a trigger.编辑:我从@Aaron Bertrand 在这里使用触发器的优秀文章中找到了很多灵感。

In the current implementation of temporal tables, it records only time based information and nothing else about the session that made the change.在时态表的当前实现中,它只记录基于时间的信息,而没有关于进行更改的会话的其他信息。 And don't read that statement as me having some sort of insider knowledge that that situation may change in the future;不要因为我知道这种情况在未来可能会发生变化而阅读该声明; I don't know anything about it.我对此一无所知。 If you need that information, you will need to record it in row.如果您需要该信息,则需要将其记录在一行中。 A classic approach for doing that is to use a trigger that fires on DML operations and maintains that value on behalf of the user.执行此操作的经典方法是使用触发 DML 操作并代表用户维护该值的触发器。

Another option that I was thinking about to solve this issue is to have a LastModifiedBy field in your base temporal table that is filled when the row is saved or updated.我正在考虑解决此问题的另一个选项是在您的基本时态表中有一个 LastModifiedBy 字段,该字段在保存或更新行时填充。

This would show who modified the table and thus created the history record.这将显示谁修改了表并因此创建了历史记录。 As Aaron mentioned above, you could do it in a trigger, but my thought is to have it decided before it gets to the insert/update and put the value in the LastModifiedBy field at the time the record is updated.正如 Aaron 上面提到的,你可以在触发器中完成它,但我的想法是在它到达插入/更新之前决定它并在更新记录时将值放在 LastModifiedBy 字段中。

This would then also be in the history table each time the record is modified.每次修改记录时,这也将在历史表中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用SQL Server时态表识别更改的值? - How to identify changed values using a SQL Server temporal table? SQL Server 2016临时表为“ GENERATED”抛出不正确的语法 - SQL Server 2016 Temporal table Throwing Incorrect Syntax for “GENERATED” 如何在 SQL Server 2016 中的系统版本时态表上停止系统版本控制? - How to Stopping System-Versioning on a System-Versioned Temporal Table in SQL Server 2016? 是否可以使用Entity Framework代码优先技术在SQL Server 2016中创建时态表? - Is it possible to create a temporal table in SQL Server 2016 using Entity Framework code-first technique? 为什么我们需要SQL Server 2016中的时态表,因为我们有CDC或CT? - Why we require temporal table in SQL Server 2016 as we have CDC or CT? Sql 服务器时态表历史 - Sql Server Temporal table history Visual Studio SQL Server 2016数据库项目和临时表 - Visual Studio SQL Server 2016 database project and Temporal Tables SQL Server 2016时态表设计最佳实践 - SQL Server 2016 temporal tables design best practice NHibernate HQL Generator支持SQL Server 2016时态表 - NHibernate HQL Generator to support SQL Server 2016 temporal tables 为什么FOR子句不能与带有时态表的SQL Server 2016中的别名一起使用? - Why does the FOR Clause not work with an alias in SQL Server 2016 with temporal tables?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM