【问题标题】:SQL Server TRIGGER that will INSERT or UPDATE another table将插入或更新另一个表的 SQL Server 触发器
【发布时间】:2014-02-23 06:48:26
【问题描述】:

我有一个名为 Prices 的 SQL Server 表,其中包含数万行数据。该表被遗留应用程序大量使用,遗憾的是无法修改(不能添加、删除或修改任何列)。

我的要求是跟踪表何时被修改 (INSERT, UPDATE, or DELETE)。但是,Prices 表没有LastUpdated 列,我无法添加这样的列。此外,我的触发器必须与 SQL Server 2005 兼容。

但是,我可以创建一个额外的表 PricesHistory,它将存储 PriceIDUpdateTypeLastUpdated 列。

我想将SQL TRIGGER 附加到Prices 表中,该表将INSERTUPDATEPricesHistory 表中的一行中记录价格的最后更新时间以及触发的操作它。

这是我目前所拥有的,它将检测哪个操作导致触发器触发。但是,我对如何从 inserteddeleted 表中 SELECT 以及如何对 PricesHistory 表执行正确的 INSERT/UPDATE 感到困惑。

基本上,所有操作都应检查PriceID 是否已存在于PriceHistory 表中,UPDATE 是否已存在于UpdateTypeLastUpdated 列中。如果PriceID 尚不存在,则应将INSERTUpdateTypeLastUpdated 值一起使用。

编辑:一位同事提醒我,inserteddeleted 项目是行不是表。这意味着我可以做一个简单的IF EXISTS ... UPDATE ELSE INSERT INTO 子句。这是真的?我的印象是它将是行的表,而不是单个行。

CREATE TRIGGER PricesUpdateTrigger
ON Prices
AFTER INSERT, UPDATE, DELETE
AS
DECLARE @UpdateType nvarchar(1)
DECLARE @UpdatedDT datetime

SELECT @UpdatedDT = CURRENT_TIMESTAMP

IF EXISTS (SELECT * FROM inserted)
    IF EXISTS (SELECT * FROM deleted)
        SELECT @UpdateType = 'U'    -- Update Trigger
    ELSE
        SELECT @UpdateType = 'I'    -- Insert Trigger
ELSE
    IF EXISTS (SELECT * FROM deleted)
        SELECT @UpdateType = 'D'    -- Delete Trigger
    ELSE
        SELECT @UpdateType = NULL;  -- Unknown Operation

IF @UpdateType = 'I'
BEGIN
    -- Log an insertion record
END

IF @UpdateType = 'U'
BEGIN
    -- Log an update record
END

IF @UpdateType = 'D'
BEGIN
    -- Log a deletion record
END

GO

【问题讨论】:

  • Inserted/Updated 不是表!??不适用于 MS SQL Server。也许您的同事正在与 oracle 混合使用。对于 MS SQL Server,它可以是一个只有一行的表,但它是一个表。
  • 对不起,我的意思是它是一个单行表。就像你做一个群众 UPDATEDELETE 触发器会为每一行触发。
  • 不,触发器按每个操作触发,而不是针对每一行。如果您的操作只是更新插入一行,则它是您的应用程序特定行为。我只是想向观众澄清这一点。
  • 你的同事大错特错,插入和删除的表格可以包含多行,然后构建触发器来解决这个问题!

标签: sql-server database tsql sql-server-2005 triggers


【解决方案1】:

为什么不是通用审计表?请参阅我的演示文稿“如何预防和审核更改?”

http://craftydba.com/?page_id=880

这是一个用于保存正在更改的数据的表格。

-- 
-- 7 - Auditing data changes (table for DML trigger)
-- 


-- Delete existing table
IF OBJECT_ID('[AUDIT].[LOG_TABLE_CHANGES]') IS NOT NULL 
  DROP TABLE [AUDIT].[LOG_TABLE_CHANGES]
GO


-- Add the table
CREATE TABLE [AUDIT].[LOG_TABLE_CHANGES]
(
  [CHG_ID] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
  [CHG_DATE] [datetime] NOT NULL,
  [CHG_TYPE] [varchar](20) NOT NULL,
  [CHG_BY] [nvarchar](256) NOT NULL,
  [APP_NAME] [nvarchar](128) NOT NULL,
  [HOST_NAME] [nvarchar](128) NOT NULL,
  [SCHEMA_NAME] [sysname] NOT NULL,
  [OBJECT_NAME] [sysname] NOT NULL,
  [XML_RECSET] [xml] NULL,
 CONSTRAINT [PK_LTC_CHG_ID] PRIMARY KEY CLUSTERED ([CHG_ID] ASC)
) ON [PRIMARY]
GO

-- Add defaults for key information
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_DATE] DEFAULT (getdate()) FOR [CHG_DATE];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_TYPE] DEFAULT ('') FOR [CHG_TYPE];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_BY] DEFAULT (coalesce(suser_sname(),'?')) FOR [CHG_BY];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_APP_NAME] DEFAULT (coalesce(app_name(),'?')) FOR [APP_NAME];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_HOST_NAME] DEFAULT (coalesce(host_name(),'?')) FOR [HOST_NAME];
GO

这是一个捕获 INS、UPD、DEL 语句的触发器。

--
--  8 - Make DML trigger to capture changes
--


-- Delete existing trigger
IF OBJECT_ID('[ACTIVE].[TRG_FLUID_DATA]') IS NOT NULL 
  DROP TRIGGER [ACTIVE].[TRG_FLUID_DATA]
GO

-- Add trigger to log all changes
CREATE TRIGGER [ACTIVE].[TRG_FLUID_DATA] ON [ACTIVE].[CARS_BY_COUNTRY]
  FOR INSERT, UPDATE, DELETE AS
BEGIN

  -- Detect inserts
  IF EXISTS (select * from inserted) AND NOT EXISTS (select * from deleted)
  BEGIN
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET])
    SELECT 'INSERT', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM inserted as Record for xml auto, elements , root('RecordSet'), type)
    RETURN;
  END

  -- Detect deletes
  IF EXISTS (select * from deleted) AND NOT EXISTS (select * from inserted)
  BEGIN
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET])
    SELECT 'DELETE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type)
    RETURN;
  END

  -- Update inserts
  IF EXISTS (select * from inserted) AND EXISTS (select * from deleted)
  BEGIN
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET])
    SELECT 'UPDATE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type)
    RETURN;
  END

END;
GO

如果您对表格进行了大量更改,则可以按周期清除数据,或者像您所说的那样将修改日期记录在另一个表格中。但是,关键信息会丢失。

我的解决方案的好处在于它可以告诉您何时以及由谁进行了更改。实际数据以 XML 格式保存,如果需要可以恢复。

【讨论】:

  • 这真的很整洁,虽然乍一看可能有点过头了。我会看看它,看看我是否可以做类似的事情。我肯定喜欢这个主意!
  • 下载我的演示包。创建 AUTOS 数据库并进行操作。这并不难。
  • 我喜欢这个主意!我想我会修改并使用我们的删除审计模板,但我更喜欢旧/新值用于更新,PK 仅用于插入。需要注意的一件事是,如果您有大量插入、更新或删除,那么您可能会达到 XML 数据类型的 2GB 最大限制,这可能会导致您的事务终止(尽管它必须是大量数据正在更新)。
  • 可以,但是我一般是小批量删除大数据。 craftydba.com/?p=3079 非常适合小改动的表格。我们将其用于审计跟踪的关键配方数据。
【解决方案2】:

我从那里提供的代码可以查看如何编写单个触发器来处理数据库插入和更新,以及如何按触发器进行审计跟踪。希望对您有所帮助。

CREATE TRIGGER TRG_HourSheet ON EditedHourSheet 
FOR INSERT, UPDATE
AS                     
   DECLARE @v_xml   XML,
   @PKValue INT,                    
   @type   CHAR(1),
   @v_slno   INT               

BEGIN                    
 SET NOCOUNT ON                    


IF EXISTS(SELECT * FROM INSERTED)
BEGIN
  IF EXISTS(SELECT * FROM DELETED)
  BEGIN
     SET @type ='U';
  END
  ELSE
  BEGIN
    SET @type ='I';
  END
END

IF @type = 'U'
BEGIN   
    DECLARE DB_CURSOR CURSOR FOR                     
    SELECT ID FROM DELETED ORDER BY ModDate DESC                     

    OPEN DB_CURSOR                      
    FETCH NEXT FROM DB_CURSOR INTO @PKValue                      
    WHILE @@FETCH_STATUS = 0                       
    BEGIN                    
    SET  @v_xml =(SELECT * FROM DELETED Where ID=@PKValue          
       FOR xml AUTO, root('Record'),elements XSINIL)                    

    SELECT @v_slno = IsNull(Max(RowID),0)+1 FROM EditedHourSheetLog  
    Where HourSheetID=@PKValue           

    INSERT INTO EditedHourSheetLog(HourSheetID,XMLData,Action,RowID)                    
    values (@PKValue,@v_xml,@type,@v_slno)                    
    FETCH NEXT FROM DB_CURSOR INTO @PKValue                      

    END                    

    CLOSE DB_CURSOR                      
    DEALLOCATE DB_CURSOR                    
    --END 
END 
ELSE IF @type = 'I'
BEGIN   
    DECLARE DB_CURSOR CURSOR FOR                     
    SELECT ID FROM INSERTED ORDER BY ModDate DESC                     

    OPEN DB_CURSOR                      
    FETCH NEXT FROM DB_CURSOR INTO @PKValue                      
    WHILE @@FETCH_STATUS = 0                       
    BEGIN                    
    SET  @v_xml =(SELECT * FROM INSERTED Where ID=@PKValue          
       FOR xml AUTO, root('Record'),elements XSINIL)                    

    SELECT @v_slno = IsNull(Max(RowID),0)+1 FROM EditedHourSheetLog  
    Where HourSheetID=@PKValue           

    INSERT INTO EditedHourSheetLog(HourSheetID,XMLData,Action,RowID)                    
    values (@PKValue,@v_xml,@type,@v_slno)                    
    FETCH NEXT FROM DB_CURSOR INTO @PKValue                      

    END                    

    CLOSE DB_CURSOR                      
    DEALLOCATE DB_CURSOR                    
    --END 
END
END

【讨论】:

  • 在任何情况下都不要在触发器中使用光标。
  • 如果使用 update 语句手动更新大量行,则触发器可能无法捕获所有更新,这就是我使用游标的原因。我很清楚。
  • 游标比基于集合的东西要慢,如果更新了很多记录,那么您将遇到性能问题,可能会导致表格阻塞数小时。我从我们的一个触发器中删除了一个游标,并将 40,000 条记录的插入时间从 40 分钟缩短到了几秒钟。触发器必须尽可能快地运行。使用使用 select 而不是 values 子句的插入。
  • 我的问题是,如果有人更新多行,那么如果在触发器内部不使用游标,您的触发器如何捕获这些更新的行。你能给我举个例子吗?
  • 您加入到插入和/或删除的表中。您永远不会将任何内容设置为标量变量,并且如果您要插入另一个表,则使用 select 而不是 values 子句进行插入。
猜你喜欢
  • 2011-01-15
  • 1970-01-01
  • 2020-04-07
  • 1970-01-01
  • 2011-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多