如何在 SQL Server 表中添加“最后修改”和“创建”列?

Posted

技术标签:

【中文标题】如何在 SQL Server 表中添加“最后修改”和“创建”列?【英文标题】:How do I add a “last modified” and "created" column in a SQL Server table? 【发布时间】:2015-10-16 13:34:50 【问题描述】:

我正在为 SQL Server 2012 数据库设计一个新的数据库架构。

每个表应该有两个额外的列,称为modifiedcreated,一旦插入或更新一行,它们就会自动更改。

我不知道到达那里的最佳方式是什么。

我认为触发器是处理它的最佳方式。

我试图找到带有触发器的示例。但我发现的教程在另一个表中插入数据等。

我认为这是一个很常见的情况,但我还没有找到答案。

【问题讨论】:

以防万一有人想在 mysql-phpmyadmin 中执行此操作,这很容易。在创建表或添加新表期间,将“default”设置为“CURRENT_TIMESTAMP”并将“attributes”设置为“on update CURRENT_TIMESTAMP”。这两个都可以通过下拉设置。 【参考方案1】:

created 列很简单 - 只是一个带有默认约束的 DATETIME2(3) 列,该约束会在插入新行时设置:

Created DATETIME2(3) 
   CONSTRAINT DF_YourTable_Created DEFAULT (SYSDATETIME())

因此,当您在YourTable 中插入一行并且没有为Created 指定值时,它将被设置为当前日期和时间。

modified 需要做更多的工作,因为您需要为AFTER UPDATE 案例编写触发器并更新它 - 您不能以声明方式告诉 SQL Server 为您执行此操作....

Modified DATETIME2(3)

然后

CREATE TRIGGER updateModified
ON dbo.YourTable
AFTER UPDATE 
AS
   UPDATE dbo.YourTable
   SET modified = SYSDATETIME()
   FROM Inserted i
   WHERE dbo.YourTable.PrimaryKey = i.PrimaryKey

您需要加入 Inserted 伪表,其中包含 所有行,这些行已使用您的基表在该表的 主键 上进行了更新。

您必须为每个要在其中包含 modified 列的表创建此 AFTER UPDATE 触发器。

【讨论】:

非常感谢@marc_s!再次!非常感谢您的帮助!我很想展示一个小的演示 sql 脚本,人们可以复制和修改它,但它对于评论来说太大了。只是一个提示。我发现默认约束的简单方法是CREATE TABLE [dbo].[Table1]( [created] [datetime2](3) NOT NULL CONSTRAINT [DF_Table1_created] DEFAULT (sysdatetime()), ... ) 加上触发器CREATE TRIGGER updateModified,它就完成了!再来一个提示。对于架构中的表,触发器名称必须是唯一的。 CREATE TRIGGER dbo.Table1_updateModified。干杯斯特凡 这对于自动化来说绝对是绝妙的。谢谢你们俩。 我认为添加 SET NOCOUNT ON 语句以提高性能是明智的,正如其他答案所建议的那样。【参考方案2】:

一般可以有以下几列:

最后修改者 LastModifiedOn 创建者 创建日期

其中LastModifiedByCreatedBy 是对users 表(UserID) 的引用,LastModifiedOnCreatedOn 列是日期和时间列。

您有以下选择:

    没有触发器的解决方案 - 我在某处读到“编写触发器的最佳方法是不要编写触发器。”您应该知道它们通常会损害性能。因此,如果您可以避免它们,最好这样做,即使使用触发器在某些情况下也可能看起来最简单。

    因此,只需编辑所有 INSERTUPDATE 语句以包含当前的 UserID 以及当前日期和时间。如果无法定义这样的user ID(匿名用户),您可以使用0,列的默认值(如果没有指定user ID,则为NULL)。当您看到 NULL 值被插入时,您应该找到“有罪”语句并对其进行编辑。

    使用触发器的解决方案 - 您可以创建 AFTER INSERT, UPDATE 触发器并在那里填充用户列。在触发器的上下文中获取当前日期和时间很容易(例如使用GETUTCDATE())。这里的问题是触发器不允许传递/接受参数。因此,由于您没有插入 user ID 值,因此您无法将其传递给触发器。如何找到当前用户?

    您可以使用SET CONTEXT_INFO 和CONTEXT_INFO。在所有 insertupdate 语句之前,您必须使用 SET CONTEXT_INFOcurrent user ID 添加到当前上下文中,并在触发器中使用 CONTEXT_INFO 函数来提取它。

因此,在使用触发器时,您再次需要编辑所有 INSERTUPDATE 子句 - 这就是我不想使用它们的原因。

无论如何,如果您只需要日期和时间列而不是按列创建/修改,那么使用触发器会更持久、更容易,因为您现在和将来都不会编辑任何其他语句。


使用SQL Server 2016,我们现在可以使用SESSION_CONTEXT 函数来读取会话详细信息。使用sp_set_session_context(如read-onlyread and write)设置详细信息。这些东西有点用户友好:

EXEC sp_set_session_context 'user_id', 4;  
SELECT SESSION_CONTEXT(N'user_id');  

一个不错的example。

【讨论】:

非常感谢@gotqn 的回答!我试图通过源代码来管理它,因为实际上我也需要LastModifiedByCreatedBy。我正在使用来自 Adam Schroder 的 NPoco 和存储库模式,我喜欢它!但是我对通过代码处理它的方法有点挣扎。我是 ORM 世界的新手。但我会继续努力;) 触发问题?【参考方案3】:

注意,以上工作正常,但并非在所有情况下, 我浪费了很多时间,发现这很有帮助:

create TRIGGER yourtable_update_insert
ON yourtable
AFTER UPDATE 
as
begin
   set nocount on;
   update yourtable set modified=getdate(), modifiedby = suser_sname()
   from  yourtable t 
   inner join inserted i on t.uniqueid=i.uniqueid 
end
go

set nocount on; 是必需的,否则会出现错误:


Microsoft SQL Server 管理工作室


没有更新任何行。

第 5 行中的数据未提交。 错误来源:Microsoft.SqlServer.Management.DataTools。 错误消息:更新或删除的行值要么不使行唯一,要么改变多行(2行)。

更正错误并重试或按 ESC 取消更改。


确定帮助


【讨论】:

在代码块中格式化您的答案将有助于提高可读性和理解性。【参考方案4】:
CREATE TRIGGER [dbo].[updateModified]
   ON  [dbo].[Transaction_details] 
   AFTER UPDATE
AS 
BEGIN
    SET NOCOUNT ON;
    UPDATE dbo.Transaction_details
    SET ModifedDate = GETDATE() FROM dbo.Transaction_details t JOIN inserted i ON 
    t.TransactionID = i.TransactionID--SYSDATETIME()
END

【讨论】:

我将上面的代码用于修改后的列,并将其用于创建的列...我更改了列,因为我已经创建了它alter table Table_Name add CONSTRAINT constraint_name DEFAULT (SYSDATETIME()) for Column_Name 【参考方案5】:

要考虑的重要一点是,您应该始终让所有表和行的插入/更新时间来自同一时间源。有一个危险——如果你不使用触发器——直接更新你的表的不同应用程序将在它们的时钟上有不同时间的机器上,或者在应用程序层中本地与 UTC 的使用不一致.

考虑这样一种情况,即进行插入或更新查询并直接设置更新/修改时间值的系统的时钟落后 5 分钟(不太可能,但值得考虑),或者使用的是本地时间而不是 UTC。如果另一个系统使用 1 分钟的间隔轮询,它可能会错过更新。

出于多种原因,我从不将我的表直接暴露给应用程序。为了处理这种情况,我在表上创建了一个视图,明确列出了要访问的字段(包括更新/修改的时间字段)。然后,我在视图上使用 INSTEAD OF UPDATE、INSERT 触发器并使用数据库服务器的时钟显式设置 updatedAt 时间。这样我可以保证数据库中所有记录的时基是相同的。

这有几个好处:

    它只对基表进行一次插入,您不必 担心级联触发器被调用 它允许我在现场级别控制我公开的信息 到业务层或我的数据的其他消费者 它允许我独立于基表来保护视图

它在 SQL Azure 上运行良好。

看看这个视图上的触发器示例:

ALTER TRIGGER [MR3W].[tgUpdateBuilding] ON [MR3W].[vwMrWebBuilding]
INSTEAD OF UPDATE, INSERT AS
BEGIN

SET NOCOUNT ON

IF EXISTS(SELECT * FROM DELETED)
BEGIN
    UPDATE [dbo].[Building]
       SET 
          ,[BuildingName] = i.BuildingName
          ,[isActive] = i.isActive
          ,[updatedAt] = getdate()
     FROM dbo.Building b
     inner join inserted i on i.BuildingId = b.BuildingId
END
ELSE
BEGIN
INSERT INTO [dbo].[Building]
           (
            [BuildingName]
           ,[isActive]
           ,[updatedAt]
           )

           SELECT 
               [BuildingName]
              ,[isActive]
              ,getdate()
              FROM INSERTED
    END
END

我希望这会有所帮助,如果有理由这不是最佳解决方案,我会欢迎 cmets。

【讨论】:

我正在寻找使用这种方法的保证,因为我刚刚发现了 INSTEAD OF 触发器(我是 Sql Server 的新手)。我认为它比 AFTER 触发方法在性能方面要好,因为与添加更新操作相比,它用包含该数据的操作替换了传入操作。但是,如果插入/更新很少并且影响很多行,例如在数据仓库中,并且单行操作不多,则差异可能很小。不太好的方面是,如果我们向表中添加列,我们应该更新触发器定义以添加新列?【参考方案6】:

此解决方案可能不适用于所有用例,但在可能的情况下,它是一种非常干净的方式。 创建用于在表中插入/更新行的存储过程,并且仅使用此 sp 来修改表。在存储过程中,您始终可以根据需要设置创建和更新的列。例如设置 updatedTime = GetUTCTime()

【讨论】:

以上是关于如何在 SQL Server 表中添加“最后修改”和“创建”列?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SQL Server 的表中添加 2 个外键?

如何在 SQL Server 2008 R2 表中添加“上次更新”列?

如何在 SQL Server 中查找上次修改表的人?

sql server中怎么给表中增加一列?

在SQL SERVER 的表中,插入新的字段

使用 c# 将 MS Access 表数据添加到 SQL Server 表中