Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 101883
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 11, 20262026-05-11T00:51:24+00:00 2026-05-11T00:51:24+00:00

In my SQL Server backend for my app, I want to create history tables

  • 0

In my SQL Server backend for my app, I want to create history tables for a bunch of my key tables, which will track a history of changes to the rows.

My entire application uses Stored Procedures, there is no embedded SQL. The only connection to the database to modify these tables will be through the application and the SP interface. Traditionally, shops I’ve worked with have performed this task using triggers.

If I have a choice between Stored Procedures and Triggers, which is better? Which is faster?

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. 2026-05-11T00:51:25+00:00Added an answer on May 11, 2026 at 12:51 am

    Triggers.

    We wrote a GUI (internally called Red Matrix Reloaded) to allow easy creation/management of audit logging triggers.

    Here’s some DDL of the stuff used:


    The AuditLog table

    CREATE TABLE [AuditLog] (     [AuditLogID] [int] IDENTITY (1, 1) NOT NULL ,     [ChangeDate] [datetime] NOT NULL CONSTRAINT [DF_AuditLog_ChangeDate] DEFAULT (getdate()),     [RowGUID] [uniqueidentifier] NOT NULL ,     [ChangeType] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,     [TableName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,     [FieldName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,     [OldValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,     [NewValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,     [Username] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,     [Hostname] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,     [AppName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,     [UserGUID] [uniqueidentifier] NULL ,     [TagGUID] [uniqueidentifier] NULL ,     [Tag] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL  ) 

    Trigger to log inserts

    CREATE TRIGGER LogInsert_Nodes ON dbo.Nodes FOR INSERT AS  /* Load the saved context info UserGUID */ DECLARE @SavedUserGUID uniqueidentifier  SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier) FROM master.dbo.sysprocesses WHERE spid = @@SPID  DECLARE @NullGUID uniqueidentifier SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'  IF @SavedUserGUID = @NullGUID BEGIN     SET @SavedUserGUID = NULL END      /*We dont' log individual field changes Old/New because the row is new.     So we only have one record - INSERTED*/      INSERT INTO AuditLog(             ChangeDate, RowGUID, ChangeType,              Username, HostName, AppName,             UserGUID,              TableName, FieldName,              TagGUID, Tag,              OldValue, NewValue)      SELECT         getdate(), --ChangeDate         i.NodeGUID, --RowGUID         'INSERTED', --ChangeType         USER_NAME(), HOST_NAME(), APP_NAME(),          @SavedUserGUID, --UserGUID         'Nodes', --TableName         '', --FieldName         i.ParentNodeGUID, --TagGUID         i.Caption, --Tag         null, --OldValue         null --NewValue     FROM Inserted i 

    Trigger to log Updates

    CREATE TRIGGER LogUpdate_Nodes ON dbo.Nodes FOR UPDATE AS  /* Load the saved context info UserGUID */ DECLARE @SavedUserGUID uniqueidentifier  SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier) FROM master.dbo.sysprocesses WHERE spid = @@SPID  DECLARE @NullGUID uniqueidentifier SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'  IF @SavedUserGUID = @NullGUID BEGIN     SET @SavedUserGUID = NULL END      /* ParentNodeGUID uniqueidentifier */     IF UPDATE (ParentNodeGUID)     BEGIN         INSERT INTO AuditLog(             ChangeDate, RowGUID, ChangeType,              Username, HostName, AppName,             UserGUID,              TableName, FieldName,              TagGUID, Tag,              OldValue, NewValue)         SELECT              getdate(), --ChangeDate             i.NodeGUID, --RowGUID             'UPDATED', --ChangeType             USER_NAME(), HOST_NAME(), APP_NAME(),              @SavedUserGUID, --UserGUID             'Nodes', --TableName             'ParentNodeGUID', --FieldName             i.ParentNodeGUID, --TagGUID             i.Caption, --Tag             d.ParentNodeGUID, --OldValue             i.ParentNodeGUID --NewValue         FROM Inserted i             INNER JOIN Deleted d             ON i.NodeGUID = d.NodeGUID         WHERE (d.ParentNodeGUID IS NULL AND i.ParentNodeGUID IS NOT NULL)         OR (d.ParentNodeGUID IS NOT NULL AND i.ParentNodeGUID IS NULL)         OR (d.ParentNodeGUID <> i.ParentNodeGUID)     END      /* Caption varchar(255) */     IF UPDATE (Caption)     BEGIN         INSERT INTO AuditLog(             ChangeDate, RowGUID, ChangeType,              Username, HostName, AppName,             UserGUID,              TableName, FieldName,              TagGUID, Tag,              OldValue, NewValue)         SELECT              getdate(), --ChangeDate             i.NodeGUID, --RowGUID             'UPDATED', --ChangeType             USER_NAME(), HOST_NAME(), APP_NAME(),              @SavedUserGUID, --UserGUID             'Nodes', --TableName             'Caption', --FieldName             i.ParentNodeGUID, --TagGUID             i.Caption, --Tag             d.Caption, --OldValue             i.Caption --NewValue         FROM Inserted i             INNER JOIN Deleted d             ON i.NodeGUID = d.NodeGUID         WHERE (d.Caption IS NULL AND i.Caption IS NOT NULL)         OR (d.Caption IS NOT NULL AND i.Caption IS NULL)         OR (d.Caption <> i.Caption)     END  ...  /* ImageGUID uniqueidentifier */ IF UPDATE (ImageGUID) BEGIN     INSERT INTO AuditLog(         ChangeDate, RowGUID, ChangeType,          Username, HostName, AppName,         UserGUID,          TableName, FieldName,          TagGUID, Tag,          OldValue, NewValue)     SELECT          getdate(), --ChangeDate         i.NodeGUID, --RowGUID         'UPDATED', --ChangeType         USER_NAME(), HOST_NAME(), APP_NAME(),          @SavedUserGUID, --UserGUID         'Nodes', --TableName         'ImageGUID', --FieldName         i.ParentNodeGUID, --TagGUID         i.Caption, --Tag         (SELECT Caption FROM Nodes WHERE NodeGUID = d.ImageGUID), --OldValue         (SELECT Caption FROM Nodes WHERE NodeGUID = i.ImageGUID) --New Value     FROM Inserted i         INNER JOIN Deleted d         ON i.NodeGUID = d.NodeGUID     WHERE (d.ImageGUID IS NULL AND i.ImageGUID IS NOT NULL)     OR (d.ImageGUID IS NOT NULL AND i.ImageGUID IS NULL)     OR (d.ImageGUID <> i.ImageGUID) END 

    Trigger to log Delete

    CREATE TRIGGER LogDelete_Nodes ON dbo.Nodes FOR DELETE AS  /* Load the saved context info UserGUID */ DECLARE @SavedUserGUID uniqueidentifier  SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier) FROM master.dbo.sysprocesses WHERE spid = @@SPID  DECLARE @NullGUID uniqueidentifier SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'  IF @SavedUserGUID = @NullGUID BEGIN     SET @SavedUserGUID = NULL END      /*We dont' log individual field changes Old/New because the row is new.     So we only have one record - DELETED*/      INSERT INTO AuditLog(             ChangeDate, RowGUID, ChangeType,              Username, HostName, AppName,             UserGUID,              TableName, FieldName,              TagGUID, Tag,              OldValue,NewValue)      SELECT         getdate(), --ChangeDate         d.NodeGUID, --RowGUID         'DELETED', --ChangeType         USER_NAME(), HOST_NAME(), APP_NAME(),          @SavedUserGUID, --UserGUID         'Nodes', --TableName         '', --FieldName         d.ParentNodeGUID, --TagGUID         d.Caption, --Tag         null, --OldValue         null --NewValue     FROM Deleted d 

    And in order to know which user in the software did the update, every connection ‘logs itself onto SQL Server’ by calling a stored procedure:

    CREATE PROCEDURE dbo.SaveContextUserGUID @UserGUID uniqueidentifier AS  /* Saves the given UserGUID as the session's 'Context Information' */ IF @UserGUID IS NULL BEGIN     PRINT 'Emptying CONTEXT_INFO because of null @UserGUID'     DECLARE @BinVar varbinary(128)     SET @BinVar = CAST( REPLICATE( 0x00, 128 ) AS varbinary(128) )     SET CONTEXT_INFO @BinVar     RETURN 0 END  DECLARE @UserGUIDBinary binary(16) --a guid is 16 bytes SELECT @UserGUIDBinary = CAST(@UserGUID as binary(16)) SET CONTEXT_INFO @UserGUIDBinary   /* To load the guid back  DECLARE @SavedUserGUID uniqueidentifier  SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier) FROM master.dbo.sysprocesses WHERE spid = @@SPID  select @SavedUserGUID AS UserGUID */ 

    Notes

    • Stackoverflow code format removes most blank lines – so formatting sucks
    • We use a table of users, not integrated security
    • This code is provided as a convience – no critisism of our design selection allowed. Purists might insist that all logging code should be done in the business layer – they can come here and write/maintain it for us.
    • blobs cannot be logged using triggers in SQL Server (there is no ‘before’ version of a blob – there is only what is). Text and nText are blobs – which makes notes either unloggable, or makes them varchar(2000)’s.
    • the Tag column is used as an arbitrary text to identify the row (e.g. if a customer was deleted, the tag will show ‘General Motors North America’ in the audit log table.
    • TagGUID is used to point to the row’s ‘parent’. For example logging InvoiceLineItems points back to the InvoiceHeader. This way anyone searching for audit log entries related for a specific invoice will find the deleted ‘line items’ by the line item’s TagGUID in the audit trail.
    • sometimes the ‘OldValue’ and ‘NewValue’ values are written as a sub-select – to get a meaningful string. i.e.’

      OldValue: {233d-ad34234..} NewValue: {883-sdf34…}

    is less useful in the audit trail than:

    OldValue: Daimler Chrysler NewValue: Cerberus Capital Management 

    Final note: Feel free to not do what we do. This is great for us, but everyone else is free to not use it.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Ask A Question

Stats

  • Questions 123k
  • Answers 123k
  • Best Answers 0
  • User 1
  • Popular
  • Answers
  • Editorial Team

    How to approach applying for a job at a company ...

    • 7 Answers
  • Editorial Team

    How to handle personal stress caused by utterly incompetent and ...

    • 5 Answers
  • Editorial Team

    What is a programmer’s life like?

    • 5 Answers
  • Editorial Team
    Editorial Team added an answer Yes, doing significant work in the constructor is usually a… May 12, 2026 at 1:01 am
  • Editorial Team
    Editorial Team added an answer Your group by will need to match the data you're… May 12, 2026 at 1:00 am
  • Editorial Team
    Editorial Team added an answer You can add a click event to the "html" element.… May 12, 2026 at 1:00 am

Related Questions

What I am asking, is what is the best way (i.e. the way that
I'm part of a team building an ADO.NET based web-site. We sometimes have several
We have a 12-year-old Ms Access app that we use for our core inventory
I am currently working on a VB.NET desktop application that uses .mdb (Access) database

Trending Tags

analytics british company computer developers django employee employer english facebook french google interview javascript language life php programmer programs salary

Top Members

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.