views:

336

answers:

4

What I am trying to do is find out which fields were updated and record the change to a different table.

DECLARE 
    @BillNo int,
    @column_name varchar(500)  

SELECT @BillNo = BillNo FROM INSERTED
DECLARE HistoryMonitorLoop CURSOR FOR
    SELECT    
        column_name 
    FROM 
        information_schema.columns
    WHERE 
        table_name = 'Shipment';
OPEN HistoryMonitorLoop
FETCH next FROM HistoryMonitorLoop INTO @column_name
WHILE @@Fetch_status = 0
BEGIN
    DECLARE
        @OldValue varchar(500),
        @NewValue varchar(500)
    SET @OldValue = (SELECT @column_name FROM Deleted);
    SET @NewValue = (SELECT @column_name FROM Inserted);
    IF(@OldValue != @NewValue)
    BEGIN
        DECLARE @Comment varchar(5000)
        SELECT @Comment = @column_name + ' Changed from ' + @OldValue + ' to ' + @NewValue
        EXEC ShipmentNote_Insert @BillNo=@BillNo,@CoordinatorID=1,@Comment=@Comment
    END
    FETCH next FROM HistoryMonitorLoop INTO @column_name
END
CLOSE HistoryMonitorLoop
DEALLOCATE HistoryMonitorLoop

what is happening is the
SET @OldValue = (SELECT @column_name FROM Deleted);
SET @NewValue = (SELECT @column_name FROM Inserted);

are setting the @OldValue and @NewValue = to the columnname instead of the value of the column – sql is processing it as SET @OldValue = (SELECT 'column_name' FROM Deleted);

A: 

This wont work:

SET @OldValue = (SELECT @column_name FROM Deleted);
SET @NewValue = (SELECT @column_name FROM Inserted);

You're attempting dynamic sql here, which won't work. You have to hard-code the SQL, the variable @column_name will not be dynamically replaced with its value, because the SQL of the trigger gets parsed once, before the trigger runs. With this, you'll (depending on your settings) probably get the literal value of the column name.

It is possible to get dynamic SQL (by connecting to the server in another process, or in MySQl by creating a prepared statement), but it's not possible to do that and reference the "magic" INSERTED and DELETED pseudo-tables available in a trigger.

So your clever use of information_schema.columns won't work. What you can do is leverage that cleverness to write a stored procedure to generate the trigger (this is in fact what I did when I had to write auditing triggers). Then whenever you change the Shipment table, you'll have to run the sp to generate the "create trigger...." statmentnt, and then run that generated statement to re-create the trigger.

tpdi
the ShipmentNote_Insert adds in a datestamp so that isn't an issue. as far as the performance hit the data in the shipment table itself doesn't get updated often but when it does we need to know what was changed, a third party app uses the notices. the structure of the table is very fluid right now
Christopher Kelly
Ok, then, what's your question?
tpdi
+3  A: 

What I am trying to do is find out which fields were updated

In SQL Server there are two functions that does exactly what you are looking for.

  • Columns_Updated() - Check if one or more column(s) is/are inserted/deleted within trigger
  • Update() - Check if a single column is updated within trigger
Sung Meister
+1  A: 

See this Pop on the Audit Trail It uses a query in a loop as opposed to a cursor, to do just what you're wanting to do.

MikeW
that did it, now all i have to do is figure out how ;)
Christopher Kelly
A: 

I'd rethink your whole process. Triggers can be huge performance killers when written improperly. Anytime you think you need to use a cursor or a loop, think again. You need to do this in a set-based fashion.

We use a two table trigger approach. One that records the details about when and who changed the table and a related table that contains the information that was changed. This helps us see all records that were changed at one time. We use an updated statement for each field to populate the second table something like:

if (update([test]))
  begin
    insert [myAudit].dbo.[mytableAuditLogDetail](AuditLogID, ID, ColumnName,   
                                                 OldValue, NewValue)
    select
      @AuditLogID,
      i.[mytableid]),
      'test',
      convert(varchar(8000), d.[test], 0),
      convert(varchar(8000), i.[test], 0)
    from  inserted i
    inner join deleted d on i.[mytableid]=d.[mytableid]
      and (
      (i.[test] <> d.[test]) or 
      (i.[test] is null and d.[test] Is Not Null) or
      (i.[test] is not null and d.[test] Is Null)
          )   
   end

We rebuild the trigger code dynamically every time the schema is changed, but the trigger itself is not dynamic. Our trigger process runs very fast even when we do large imports.

HLGEM