如何添加“上次修改”和"创建" SQL Server表中的列?

时间:2015-07-26 08:09:19

标签: sql-server tsql triggers sql-server-2012

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

每个表都应该有两个额外的列modifiedcreated,这些列应该在插入或更新行时自动更改。

我不知道怎样才能到达那里。

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

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

我认为这是一个非常常见的场景,但我还没有找到答案。

5 个答案:

答案 0 :(得分:39)

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伪表,其中包含所有行,这些表已在您的主键上使用您的基表进行更新。

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

答案 1 :(得分:8)

通常,您可以拥有以下列:

  • LastModifiedBy
  • LastModifiedOn
  • CreatedBy
  • CreatedOn

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

您有以下选择:

  1. 没有触发器的解决方案 - 我在某处读过“编写触发器的最佳方法是不写这样的。”并且您应该知道它们通常会损害性能。所以,如果你可以避免它们,最好这样做,即使使用触发器在某些情况下看起来最容易做。

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

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

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

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

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

    使用SQL Server 2016,我们现在可以使用SESSION_CONTEXT函数来读取会话详细信息。详细信息使用sp_set_session_contextread-onlyread and write)进行设置。事情有点用户友好:

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

    一个不错的example

答案 2 :(得分:1)

Attention, above works fine but not in all cases, I lost a lot of time and found this helpfull:

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; is needed else you get the error:


Microsoft SQL Server Management Studio


No row was updated.

The data in row 5 was not committed. Error Source: Microsoft.SqlServer.Management.DataTools. Error Message: The row value(s) updated or deleted either do not make the row unique or they alter multiple rows(2 rows).

Correct the errors and retry or press ESC to cancel the change(s).


OK Help


答案 3 :(得分:0)

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

答案 4 :(得分:0)

要考虑的重要一件事是,所有表和行的插入/更新时间应始终来自同一时间源。如果不使用触发器,则存在危险,即直接对表进行更新的不同应用程序将位于时钟时间不同的计算机上,或者在应用程序层中不会一致使用本地与UTC

请考虑以下情况:进行直接设置更新/修改时间值的插入或更新查询的系统晚5分钟(不太可能,但值得考虑),或者使用本地时间与UTC。如果另一个系统每隔1分钟轮询一次,则可能会错过更新。

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

这有一些好处:

  1. 它只需在基表中插入一个,而您不必 担心级联触发器称为
  2. 它允许我在现场级别控制我公开的信息 到业务层或我的数据的其他使用者
  3. 它使我能够独立于基表保护视图

它在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

我希望这会有所帮助,如果有某些原因不是最佳解决方案,欢迎提出评论。