How to DELETE on a table using the table's name - sql-server

I'm trying to write a trigger that checks for if a bit is true before deleting a table, and setting it as inactive if said bit is false. My trigger was this:
DELETE Contacts
FROM Contacts
INNER JOIN deleted ON Contacts.ContactID = deleted.ContactID
WHERE deleted.AllowDelete = 1
UPDATE Contacts
SET Active = 0
FROM Contacts
INNER JOIN deleted on Contacts.ContactID = deleted.ContactID
WHERE deleted.allowDelete = 0
Where Contacts is the table someone is trying to delete from. However, using this trigger on every table where it is necessary seemed inefficient, so I'm trying to normalize it with a stored procedure.
The idea is to exec the SP with the tablename as a variable, and the deleted table put into a temptable. Right now the trigger looks like this:
SELECT *
INTO #deleted
FROM deleted
DROP TABLE #deleted
And the SP looks like this:
ALTER PROCEDURE [dbo].[OnDeleteTrigger]
-- Add the parameters for the stored procedure here
#TableToDeleteFrom nvarchar(max) = ''
AS
BEGIN
SET NOCOUNT ON;
DECLARE
DELETE Contacts
FROM Contacts
INNER JOIN #deleted ON Contacts.ContactID = #deleted.ContactID
WHERE #deleted.AllowDelete = 1
Update Contacts
SET Active = 0
FROM Contacts
INNER JOIN #deleted ON Contacts.ContactID = #deleted.ContactID
WHERE #deleted.AllowDelete = 1
END
The deleted temptable seems to work fine, although I can't test it yet as I can't find a way to get the table dbo from the table name, to replace all the 'Contacts'.
Hopefully this is enough information to get an answer, if not I'll edit it later.

Having a separate trigger on each table with static SQL is more efficient, albeit doesn't lend itself well to code reuse. The other tables will have different primary key column names than ContactID so you would need to pass that name as well as the table name.
With the desired tables tables all having standard columns Active and AllowDelete, you could create the needed triggers dynamically during deployment rather than dynamic SQL at run time. Below is an example of this technique, using MERGE instead of separate INSERT and UPDATE statements:
CREATE TABLE dbo.Contacts(
ContactID int NOT NULL CONSTRAINT PK_Contacts PRIMARY KEY
, Active bit NOT NULL
, AllowDelete bit NOT NULL
);
CREATE TABLE dbo.Users(
UserID int NOT NULL CONSTRAINT PK_Users PRIMARY KEY
, Active bit NOT NULL
, AllowDelete bit NOT NULL
);
INSERT INTO dbo.Contacts VALUES(1, 1, 0);
INSERT INTO dbo.Contacts VALUES(2, 1, 0);
INSERT INTO dbo.Contacts VALUES(3, 1, 1);
INSERT INTO dbo.Contacts VALUES(4, 1, 1);
INSERT INTO dbo.Users VALUES(1, 1, 0);
INSERT INTO dbo.Users VALUES(2, 1, 0);
INSERT INTO dbo.Users VALUES(3, 1, 1);
INSERT INTO dbo.Users VALUES(4, 1, 1);
GO
CREATE PROC dbo.CreateSoftDeleteTrigger
#SchemaName sysname
, #TableName sysname
, #JoinCriteria nvarchar(MAX)
AS
SET NOCOUNT ON;
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'CREATE TRIGGER ' + QUOTENAME(N'TRD_' + #TableName) + N'
ON ' + QUOTENAME(#SchemaName) + N'.' + QUOTENAME(#TableName) + N'
INSTEAD OF DELETE
AS
SET NOCOUNT ON;
MERGE ' + QUOTENAME(#SchemaName) + N'.' + QUOTENAME(#TableName) + N' AS target
USING deleted ON ' + #JoinCriteria + N'
WHEN MATCHED AND deleted.AllowDelete = 1 THEN DELETE
WHEN MATCHED AND deleted.AllowDelete = 0 THEN UPDATE SET Active = 0;'
EXECUTE(#SQL);
GO
EXEC dbo.CreateSoftDeleteTrigger
#SchemaName = N'dbo'
, #TableName = N'Contacts'
, #JoinCriteria = N'target.ContactID = deleted.ContactID';
EXEC dbo.CreateSoftDeleteTrigger
#SchemaName = N'dbo'
, #TableName = N'Users'
, #JoinCriteria = N'target.UserID = deleted.UserID ';
GO
--soft delete test
DELETE FROM dbo.Contacts WHERE ContactID = 1;
SELECT * FROM Contacts;
--hard delete test
DELETE FROM dbo.Users WHERE UserID = 4;
SELECT * FROM Users;
GO

I guess you want to use the trigger "instead of":
https://technet.microsoft.com/en-us/library/ms175521(v=sql.105).aspx
CREATE TABLE Contacts
(
id int identity(1,1) primary key clustered,
[name] varchar(50),
isactive bit not null default(1),
SoftDeletion bit not null default(1)
)
insert into Contacts([name]) values ('my'),('myself'),('I');
insert into Contacts([name],SoftDeletion) values ('Killable', 0);
GO
CREATE TRIGGER trgInsteadOfDelete ON Contacts
INSTEAD OF DELETE
AS
BEGIN
UPDATE Contacts
set isactive = 0
where
SoftDeletion = 1
and
id in (SELECT ID from deleted);
DELETE FROM Contacts
WHERE
softdeletion = 0
AND
id in (SELECT ID from deleted);
END
GO
SELECT * from Contacts;
DELETE FROM Contacts;
SELECT * from Contacts;

Related

SQL Stored Procedure with Input parameters with While loop

I have a code below that should insert records into the table but unfortunately this code foes not work in case multiple records are inserted or updated or deleted. How should I rewrite the code for procedure to loop through all the inserted / deleted records? And I do need to use that stored procedure with Input parameters (not just simple insert into ... select ... from ...)
IF EXISTS (SELECT * FROM MyDB.sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[MyTable_DEL_UPD_INS]'))
DROP TRIGGER [dbo].[MyTable_DEL_UPD_INS]
GO
CREATE TRIGGER [dbo].[MyTable_DEL_UPD_INS]
ON [MyDB].[dbo].[MyTable]
AFTER DELETE, UPDATE, INSERT
NOT FOR REPLICATION
AS
BEGIN
DECLARE #PKId INT,
#Code VARCHAR(5),
#AuditType VARCHAR(10)
SET #Code = 'TEST'
IF EXISTS (SELECT * FROM deleted d)
AND NOT EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'DELETE'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
IF EXISTS (SELECT * FROM deleted d)
AND EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'UPDATE'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
IF NOT EXISTS (SELECT * FROM deleted d)
AND EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'INSERT'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
END
GO
ALTER TABLE [MyDB].[dbo].[MyTable] ENABLE TRIGGER [MyTable_DEL_UPD_INS]
You should avoid using loops in triggers.
Triggers should be as quick to run as possible, since SQL Server will not return control to whatever statement that fired the trigger until the trigger is completed.
So instead of a loop, you should modify your SP_Audit procedure to work with multiple records instead of a single one.
usually, this is easily be done using a table valued parameter.
If you could post the SP_Audit as well, we could give you a complete solution.
Since you didn't post it, you can use these guidelines as a start:
First, you create a user defined table type:
CREATE TYPE dbo.Ids AS TABLE
(
Id int NOT NULL PRIMARY KEY
)
GO
Then, you create the procedure to use it:
CREATE PROCEDURE [dbo].[STP_Audit_MultipleRecords]
(
#IDs dbo.Ids readonly,
#Code CHAR(4),
#AuditType CHAR(6)
)
AS
-- Implementation here
GO
Last, your write your trigger like this:
CREATE TRIGGER [dbo].[MyTable_DEL_UPD_INS]
ON [MyDB].[dbo].[MyTable]
AFTER DELETE, UPDATE, INSERT
NOT FOR REPLICATION
AS
BEGIN
DECLARE #HasDeleted bit = 0,
#HasInserted bit = 0,
#AuditType CHAR(6),
#Code CHAR(4)
SET #Code = 'TEST'
DECLARE #IDs as dbo.Ids
IF EXISTS (SELECT * FROM deleted d)
SET #HasDeleted = 1
IF EXISTS (SELECT * FROM inserted i)
SET #HasInserted = 1
IF #HasDeleted = 1
BEGIN
IF #HasInserted = 1
BEGIN
SET #AuditType = 'UPDATE'
END
ELSE
BEGIN
SET #AuditType = 'DELETE'
END
END
ELSE
IF #HasInserted = 1
BEGIN
SET #AuditType = 'INSERT'
END
INSERT INTO #IDs (Id)
SELECT [MyTable_PK]
FROM inserted
UNION
SELECT [MyTable_PK]
FROM deleted
EXEC [dbo].[STP_Audit_MultipleRecords] #IDs, #Code, #AuditType
END
GO
Notes:
The #HasDeleted and #HasInserted variables are to allow you to only execute the EXISTS query once for every procedure.
Getting the primary key values from the deleted and inserted table is done using a single union query. Since union eliminates duplicate values, you can write this query just once. If you want to, you can write a different query for each audit type, but then you will have to repeat the same query 3 times (with different tables)
I've changed the data types of your #code and #AuditType variables to char, since they have a fixed length.

SQL Server trigger on Insert and Update

I am looking to create a SQL Server trigger that moves a record from one table to an identical replica table if the record matches a specific condition.
Questions: do I need to specify each column, or can I use a wildcard?
Can I use something like:
SET #RecID = (SELECT [RecoID] FROM Inserted)
IF NULLIF(#RecID, '') IS NOT NULL
(then insert....)
THANKS!
There's a lot of stuff you "CAN" do in a trigger, but that doesn't mean you should. I'd would urge to to avoid setting scalar variables within a trigger at all costs. Even if you 100% sure your table will never have more that 1 row inserted per transaction because that's how the app is designed... You'll be in for very rude awakening when you find out that not all transactions come through the application.
Below is a quick demonstration of both types of triggers...
USE tempdb;
GO
IF OBJECT_ID('tempdb.dbo.PrimaryTable', 'U') IS NOT NULL
DROP TABLE dbo.PrimaryTable;
GO
IF OBJECT_ID('tempdb.dbo.TriggerScalarLog', 'U') IS NOT NULL
DROP TABLE dbo.TriggerScalarLog;
GO
IF OBJECT_ID('tempdb.dbo.TriggerMultiRowLog', 'U') IS NOT NULL
DROP TABLE dbo.TriggerMultiRowLog;
GO
CREATE TABLE dbo.PrimaryTable (
Pt_ID INT NOT NULL IDENTITY (1,1) PRIMARY KEY CLUSTERED,
Col_1 INT NULL,
Col_2 DATE NOT NULL
CONSTRAINT df_Col2 DEFAULT (GETDATE())
);
GO
CREATE TABLE dbo.TriggerScalarLog (
Pt_ID INT,
Col1_Old INT,
Col1_New INT,
Col2_Old DATE,
Col2_New DATE
);
GO
CREATE TABLE dbo.TriggerMultiRowLog (
Pt_ID INT,
Col1_Old INT,
Col1_New INT,
Col2_Old DATE,
Col2_New DATE
);
GO
--=======================================================
CREATE TRIGGER dbo.PrimaryCrudScalar ON dbo.PrimaryTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
DECLARE
#Pt_ID INT,
#Col1_Old INT,
#Col1_New INT,
#Col2_Old DATE,
#Col2_New DATE;
SELECT
#Pt_ID = ISNULL(i.Pt_ID, d.Pt_ID),
#Col1_Old = d.Col_1,
#Col1_New = i.Col_1,
#Col2_Old = d.Col_2,
#Col2_New = i.Col_2
FROM
Inserted i
FULL JOIN Deleted d
ON i.Pt_ID = d.Pt_ID;
INSERT dbo.TriggerScalarLog (Pt_ID, Col1_Old, Col1_New, Col2_Old, Col2_New)
VALUES (#Pt_ID, #Col1_Old, #Col1_New, #Col2_Old, #Col2_New);
GO -- DROP TRIGGER dbo.PrimaryCrudScalar;
CREATE TRIGGER PrimaryCrudMultiRow ON dbo.PrimaryTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
INSERT dbo.TriggerMultiRowLog (Pt_ID, Col1_Old, Col1_New, Col2_Old, Col2_New)
SELECT
ISNULL(i.Pt_ID, d.Pt_ID),
d.Col_1,
i.Col_1,
d.Col_2,
i.Col_2
FROM
Inserted i
FULL JOIN Deleted d
ON i.Pt_ID = d.Pt_ID;
GO -- DROP TRIGGER dbo.TriggerMultiRowLog;
--=======================================================
--=======================================================
-- --insert test...
INSERT dbo.PrimaryTable (Col_1)
SELECT TOP 100
o.object_id
FROM
sys.objects o;
SELECT 'INSERT Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'INSERT Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
UPDATE pt SET
pt.Col_1 = pt.Col_1 + rv.RandomVal,
pt.Col_2 = DATEADD(DAY, rv.RandomVal, pt.Col_2)
FROM
dbo.PrimaryTable pt
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 10000 + 1) ) rv (RandomVal);
SELECT 'UPDATE Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'UPDATE Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
DELETE pt
FROM
dbo.PrimaryTable pt;
SELECT 'DELETE Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'DELETE Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
You could, but I'd recommend against it. If your source table changed things would start failing.
Also, in your example if you were to ever have more than one row inserted at a time you would get thrown an error (or have unpredictable results). I'd recommend a more set based approach:
INSERT table2 ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table2 t ON i.RecoID = t.RecoID
WHERE t.RecoID IS NULL;
EDIT:
If you want to stop the insert happening on your original table then you'll need to do something along the lines of:
CREATE TRIGGER trigger_name
ON table_orig
INSTEAD OF INSERT
AS
BEGIN
-- make sure we aren't triggering from ourselves from another trigger
IF TRIGGER_NESTLEVEL() <= 1
return;
-- insert into the table_copy if the inserted row is already in table_orig (not null)
INSERT table_copy ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table_orig c ON i.RecoID = c.RecoID
WHERE t.RecoID IS NOT NULL;
-- insert into table_orig if the inserted row is not already in table_orig (null)
INSERT table_orig ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table_orig c ON i.RecoID = c.RecoID
WHERE t.RecoID IS NULL;
END;
The instead of will stop the insert if you don't want it to actually be inserted, so you'll need to do that yourself (the second insert statement).
Please note I changed some nulls to not nulls and the table we are left joining to in some cases.

Select (Select field from FieldTable) from Table

I'm using MSQL 2005. I have 2 table.A and B
Table A
- ID DOVKOD
- 1 KURSATIS
Table B
- ID KURALIS KURSATIS
- 1 2,2522 2,2685
- 2 2,4758 2,4874
Table A has only 1 record
When I execute Select (Select DOVKOD from Table A) from Table B I want to get same result as Select KURSATIS from Table B
I am gonna use it in a view. How can I do that. Thanks..
You can simply use a CASE expression:
SELECT CASE WHEN (SELECT DOVKOD FROM A) = 'KURSATIS' THEN KURSATIS
ELSE KURALIS
END
FROM B
SQL Fiddle Demo here
You must use Dynamic TSQL
SELECT #column=DOVKOD from Table A
EXEC ('Select ' + #column + ' from Table B')
If I understood you right then in table A you have the name of the column that you want to return. Then your solution is bad at all. I'll rather do something like that:
CREATE TABLE #TableA
(
ID INT, DOVKOD VARCHAR(100)
);
INSERT INTO #TableA VALUES (1, 'KURSATIS');
CREATE TABLE #TableB
(
ID INT, Value DECIMAL (18,2),Name VARCHAR(100)
);
INSERT INTO #TableB VALUES (1, 2.2522 , 'KURALIS');
INSERT INTO #TableB VALUES (2, 2.4758 , 'KURSATIS');
SELECT #TableB.* FROM #TableB JOIN #TableA ON #TableA.DOVKOD = #TableB.Name
The only way how to do this in MySQL is using Prepared statements. Dynamic pivot tables (transform rows to columns) is a good article about this.
SET #sql = NULL;
Select DOVKOD INTO #sql
FROM from Table A;
SET #sql = CONCAT('SELECT ', #sql, 'FROM Table B');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

how to get all the records added for 1 last minute

I am new to SQL.
I am using SQL Server 2008.
I am trying to get records from table added for within last 1 minute.
actually I have SP which get called after every 1 minute 60sec, and I do not have any column which saved modified date, so I have to select all the rows added within minute of time interval.
It may happen no rows added or N number of rows added within last minute, so i need to get all of them added with 1 minute of timer interval, since SP will get fired after every 1 Min.
To get all records in a table that have been inserted in the last minute, you would need a date field on your table that either has a DEFAULT GETDATE() constraint, or insert the date manually when you insert the record.
SELECT *
FROM MyTable
WHERE MyDateColumn >= DATEADD(mi, -1, GETDATE())
And the create a column with the default on an existing table:
ALTER TABLE MyTable
ADD MyDateColumn DATETIME2 NOT NULL DEFAULT GETDATE()
You might be able to get table / row metadata from the sys or INFORMATION_SCHEMA schemas, but I really wouldn't recommend it.
Saying that, maybe your application / database is suitable for SQL Server Auditing?
http://technet.microsoft.com/en-us/library/cc280386.aspx
UPDATE
This doesn't exactly answer your question, but it might be a viable solution. I wrote a cursor to create and apply audit triggers to all the tables in a database a while back. The triggers are set up to log to a separate table (which is currently hard coded). You can modify this for your needs, but I've included all the relevant code below. I imagine you will need to remove the UserId and its constraints:
-- Stores the types of audit entries for the "Logs" table, such as "RECORD CREATED", "RECORD MODIFIED", "RECORD DELETED" and other, miscellaneous log types
CREATE TABLE dbo.LogTypes (
LogTypeID INT NOT NULL IDENTITY PRIMARY KEY,
[Description] VARCHAR(100) NOT NULL UNIQUE
)
CREATE TABLE dbo.[Logs] (
LogID INT NOT NULL IDENTITY PRIMARY KEY,
LogTypeID INT NOT NULL FOREIGN KEY REFERENCES LogTypes(LogTypeID),
UserID INT NOT NULL FOREIGN KEY REFERENCES Users(UserID), -- User that created this log entry.
SysObjectID INT, -- The ID of the table in sysobjects (if this is a CRUD insert from a trigger)
Details VARCHAR(1000),
DateCreated DATETIME NOT NULL DEFAULT GETDATE()
)
-----------------------------------------------------------------------------
-- Log types
-----------------------------------------------------------------------------
SET IDENTITY_INSERT LogTypes ON
INSERT INTO LogTypes (LogTypeID, [Description])
VALUES(1, 'Record Insert')
INSERT INTO LogTypes (LogTypeID, [Description])
VALUES(2, 'Record Update')
INSERT INTO LogTypes (LogTypeID, [Description])
VALUES(3, 'Record Deletion')
INSERT INTO LogTypes (LogTypeID, [Description])
VALUES(4, 'User logged in')
INSERT INTO LogTypes (LogTypeID, [Description])
VALUES(5, 'User logged out')
SET IDENTITY_INSERT LogTypes OFF
Cursor code (run once):
DECLARE #table_name VARCHAR(500), #instruction VARCHAR(MAX)
DECLARE curTables CURSOR READ_ONLY FAST_FORWARD FOR
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME <> 'Logs'
OPEN curTables
FETCH NEXT FROM curTables INTO #table_name
WHILE ##FETCH_STATUS = 0
BEGIN
-- Drop any existing trigger
SET #instruction = 'IF OBJECT_ID (''tr_' + #table_name + '_Audit'',''TR'') IS NOT NULL DROP TRIGGER tr_' + #table_name + '_Audit;'
exec sp_sqlexec #instruction
-- Create the new trigger
SET #instruction = 'CREATE TRIGGER tr_' + #table_name + '_Audit
ON ' + #table_name + '
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON
DECLARE #LogTypeID INT, #SystemUserID INT, #SysObjectID INT, #TableName VARCHAR(500)
SET #SystemUserID = 1 -- System account
SET #TableName = ''' + #table_name + '''
IF EXISTS(SELECT * FROM Inserted) AND EXISTS(SELECT * FROM Deleted)
SET #LogTypeID = 2 -- Update
ELSE IF EXISTS(SELECT * FROM Inserted)
SET #LogTypeID = 1 -- Insertion
ELSE IF EXISTS(SELECT * FROM Deleted)
SET #LogTypeID = 3 -- Deletion
SET #LogTypeID = ISNULL(#LogTypeID, 0)
IF #LogTypeID > 0
BEGIN
-- Only log if successful
SELECT
#SysObjectID = id
FROM sysobjects (nolock)
where [name] = #TableName
AND [type] = ''U''
INSERT INTO [Logs] (LogTypeID, UserID, SysObjectID, Details, DateCreated)
VALUES(#LogTypeID, #SystemUserID, #SysObjectID, NULL, GETDATE())
END'
exec sp_sqlexec #instruction
FETCH NEXT FROM curTables INTO #table_name
END
CLOSE curTables
DEALLOCATE curTables
Every table in your database will now log all INSERTs, UPDATEs and DELETEs to the Log table. However, be aware that adding triggers to your tables increases I/O and memory usage, subsequently decreasing performance. It may not be a massive problem for you, though.

SQL-Server Trigger on update for Audit

I can't find an easy/generic way to register to an audit table the columns changed on some tables.
I tried to do it using a Trigger on after update in this way:
First of all the Audit Table definition:
CREATE TABLE [Audit](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Date] [datetime] NOT NULL default GETDATE(),
[IdTypeAudit] [int] NOT NULL, --2 for Modify
[UserName] [varchar](50) NULL,
[TableName] [varchar](50) NOT NULL,
[ColumnName] [varchar](50) NULL,
[OldData] [varchar](50) NULL,
[NewData] [varchar](50) NULL )
Next a trigger on AFTER UPDATE in any table:
DECLARE
#sql varchar(8000),
#col int,
#colcount int
select #colcount = count(*) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable'
set #col = 1
while(#col < #colcount )
begin
set #sql=
'INSERT INTO Audit
SELECT 2, UserNameLastModif, ''MyTable'', COL_NAME(Object_id(''MyTable''), '+ convert(varchar,#col) +'), Deleted.'
+ COL_NAME(Object_id('MyTable'), #col) + ', Inserted.' + COL_NAME(Object_id('MyTable'), #col) + '
FROM Inserted LEFT JOIN Deleted ON Inserted.[MyTableId] = Deleted.[MyTableId]
WHERE COALESCE(Deleted.' + COL_NAME(Object_id('MyTable'), #col) + ', '''') <> COALESCE(Inserted.' + COL_NAME(Object_id('MyTable'), #col) + ', '''')'
--UserNameLastModif is an optional column on MyTable
exec(#sql)
set #col = #col + 1
end
The problems
Inserted and Deleted lost the context when I use the exec function
Seems that colnumber it isn't always a correlative number, seems if you create a table with 20 columns and you delete one and create another, the last one have a number > #colcount
I was looking for a solution for all over the net but I couln't figure out
Any Idea?
Thanks!
This highlights a greater problem with structural choice. Try to write a set-based solution. Remove the loop and dynamic SQL and write a single statement that inserts the Audit rows. It is possible but to make it easier consider a different table layout, like keeping all columns on 1 row instead of splitting them.
In SQL 2000 use syscolumns. In SQL 2005+ use sys.columns. i.e.
SELECT column_id FROM sys.columns WHERE object_id = OBJECT_ID(DB_NAME()+'.dbo.Table');
#Santiago : If you still want to write it in dynamic SQL, you should prepare all of the statements first then execute them.
8000 characters may not be enough for all the statements. A good solution is to use a table to store them.
IF NOT OBJECT_ID('tempdb..#stmt') IS NULL
DROP TABLE #stmt;
CREATE TABLE #stmt (ID int NOT NULL IDENTITY(1,1), SQL varchar(8000) NOT NULL);
Then replace the line exec(#sql) with INSERT INTO #stmt (SQL) VALUES (#sql);
Then exec each row.
WHILE EXISTS (SELECT TOP 1 * FROM #stmt)
BEGIN
BEGIN TRANSACTION;
EXEC (SELECT TOP 1 SQL FROM #stmt ORDER BY ID);
DELETE FROM #stmt WHERE ID = (SELECT MIN(ID) FROM #stmt);
COMMIT TRANSACTION;
END
Remember to use sys.columns for the column loop (I shall assume you use SQL 2005/2008).
SET #col = 0;
WHILE EXISTS (SELECT TOP 1 * FROM sys.columns WHERE object_id = OBJECT_ID('MyTable') AND column_id > #col)
BEGIN
SELECT TOP 1 #col = column_id FROM sys.columns
WHERE object_id = OBJECT_ID('MyTable') AND column_id > #col ORDER BY column_id ASC;
SET #sql ....
INSERT INTO #stmt ....
END
Remove line 4 #colcount int and the proceeding comma. Remove Information schema select.
DO not ever use any kind of looping a trigger. Do not use dynamic SQl or call a stored proc or send an email.All of these things are exretemly inappropriate in a trigger.
If tyou want to use dynamic sql use it to create the script to create the trigger. And create an audit table for every table you want audited (we actually have two for every table) or you will have performance problems due to locking on the "one table to rule them all".

Resources