How to create a before delete trigger in SQL Server? - sql-server

I want to create a before delete trigger. When I delete a record from a table that record has to be inserted into a history table. How can I do this in SQL Server?

In this situation, you're probably better off doing a regular "after" trigger. This is the most common approach to this type of situation.
Something like
CREATE TRIGGER TRG_AUD_DEL
ON yourTable
FOR DELETE
AS
INSERT INTO my_audit_table (col1, col2, ...)
SELECT col1, col2...
FROM DELETED
What will happen is, when a record (or records!) are deleted from your table, the deleted row will be inserted into my_audit_table The DELETED table is a virtual table that contains the record(s) as they were immediately prior to the delete.
Also, note that the trigger runs as part of the implicit transaction on the delete statement, so if your delete fails and rolls back, the trigger will also rollback.

You could also use INSTEAD OF DELETE
CREATE TRIGGER dbo.SomeTableYouWhatToDeleteFrom
ON dbo.YourTable
INSTEAD OF DELETE
AS
BEGIN
-- Some code you want to do before delete
DELETE YourTable
FROM DELETED D
INNER JOIN dbo.YourTable T ON T.PK_1 = D.PK_1
END

It could be done in following steps for let’s say in this example I am using customer table:
CREATE TABLE CUSTOMERS(
ID INT NOT NULL,
NAME VARCHAR (20) NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR (25) ,
LAST_UPDATED DATETIME,
PRIMARY KEY (ID)
);
Create History:
CREATE TABLE CUSTOMERS_HIST(
ID INT NOT NULL,
NAME VARCHAR (20) NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR (25) ,
LAST_UPDATED DATETIME,
PRIMARY KEY (ID)
);
Trigger on source table like below on delete event:
CREATE TRIGGER TRG_CUSTOMERS_DEL
ON CUSTOMERS
FOR DELETE
AS
INSERT INTO CUSTOMERS_HIST (ID, NAME, AGE, ADDRESS, LAST_UPDATED)
SELECT ID, NAME, AGE, ADDRESS, LAST_UPDATED
FROM DELETED

Try a trigger that executes before the delete and throws an error when the condition is not met.
CREATE TRIGGER [dbo].[TableName_PreventDeleteAndUpdate]
ON dbo.TableName
FOR DELETE, UPDATE -- runs before deletes and updates
AS
BEGIN
IF (APP_NAME() <> 'SomeApp')
BEGIN
RAISERROR ('Only delete/update with SomeApp', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END

Related

how to fire a sql trigger whenever the password change

I have a table containing id, user_Name and password. I want to create the trigger which will fire when ever the password changes.
Suppose the table is:
create table reg
(
id int identity(1,1) primary key,
userName varchar(100),
pass varchar(100)
)
and I want to save userName, password, changeDate in to below table
create table regBackUp
(
id int identity(1,1),
regId foreign key references reg(id),
oldPass varchar(100),
changeDate date
)
Well, you need to create after update trigger on reg table.
In that trigger you need to write to table regBackUp records selected from table named deleted. It is special table available in that triggers and it will hold values of reg table just before update.
See MSDN for reference about syntax of create trigger expression.
Use this. TRIGGER
You can get the old values from DELETED table
CREATE TRIGGER trgTest ON regFOR UPDATE
AS
BEGIN
IF UPDATE(pass)
BEGIN
DECLARE #id AS INT
DECLARE #pass AS varchar(100)
SELECT #id = ID, #pass = pass FROM DELETED
INSERT INTO regBackUp (regId, oldPass, changeDate)
VALUES (#id, #pass, GETDATE())
END
END
Use After Update Trigger with Update() function to find out whether column is updated or not. From docs.
indicates whether an INSERT or UPDATE attempt was made on a specified
column of a table or view. UPDATE() is used anywhere inside the body
of a Transact-SQL INSERT or UPDATE trigger to test whether the trigger
should execute certain actions.
Create a After Update trigger like this.
CREATE TRIGGER reg_pass_trg
ON reg
after UPDATE
AS
BEGIN
IF UPDATE(pass) --Works only when pass is mentioned in update statement
INSERT INTO regBackUp
(regId,oldPass,changeDate)
SELECT ID,pass,Getdate()
FROM deleted
END
INSERT INTO namepassback
(username,
pass,
[date])
SELECT username,
pass,
Getdate()
FROM namepass
WHERE id = 1
use above query that will work same without creating trigger

Is it possible to add a constraint in SQL Server 2012 to prevent new data from being entered until old data is soft deleted?

I have the following table:
CREATE TABLE [Test]
(
[Id] BIGINT IDENTITY(1, 1) NOT NULL,
[Name] CHARACTER VARYING(255) NOT NULL,
[DeletedOn] DATETIMEOFFSET NULL,
UNIQUE([Name], [DeletedOn]),
PRIMARY KEY([Id])
);
GO
I insert a new record like this:
INSERT INTO [Test] (Name, DeletedOn) VALUES ('A record', SYSDATETIMEOFFSET())
Subsequent inserts with this command will complete as expected. However, I want to require that before data with a DeletedOn value newer than an existing record with the same Name be rejected so long as a record with the same Name exists in the table with a NULL DeletedOn value.
A different way to explain this behavior would be to have you imagine a user password history. Users enter in passwords and my software hashes them and stores it in the database. A user's password expires and DeletedOn is set to the current date and time. I never want users to enter the same password over again, so that is the purpose for keeping the history. In order to maintain data consistency, I want to prevent a password from being added when there is already an active one that does not have a value in the DeletedOn column. So, if my software erroneously behaves and tries to add random passwords to a user's password history, it should fail because it would violate some constraint that prevents deletion of passwords that are not the single active password.
I originally imagined I would just wrap this logic in a stored procedure and throw an error up if such behavior was attempted, but I am curious if this could be done in a different way.
As per my knowledge There is no out of box feature to prevent new data from being entered until old data is soft deleted? We need to add some CONSTRAINT to prevent that. We can use check constraint and validate whether the combination of Name and Deleted On exists or not.
Try This
CREATE FUNCTION [dbo].[chk_RecordExists](#Name Varchar(255))
RETURNS INT
AS
BEGIN
DECLARE #Result int
SET #Result = 0
DECLARE #id AS INT
SET #id=0
SELECT #id=MAX(ID) FROM [Test] WHERE Name = #Name
IF (#id=0)
BEGIN
SET #Result = 1 -- Allow to insert as its New record
END
ELSE
BEGIN
-- Check the latest record for name if Deletedon is not null then its soft deleted can allow to insert new
IF EXISTS (SELECT ID FROM [Test] WHERE ID=#id AND Name = #Name AND DeletedOn IS NOT NULL)
BEGIN
SET #Result = 1 -- Allow to insert as its old data is soft deleted
END
END
RETURN #Result
END
GO
ALTER TABLE Test WITH NOCHECK ADD CONSTRAINT [chk_Constraint] CHECK(dbo.chk_RecordExists](Name)=0)
GO
I have never user INSTEAD OF keywords but I have read that it can be used to do exactly what you want. Below is sample code from MSDN to prevent insert of new record if there is old record. Full afticle on INSTEAD OF can be found here
CREATE TRIGGER IO_Trig_INS_Employee ON Employee
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
-- Check for duplicate Person. If there is no duplicate, do an insert.
IF (NOT EXISTS (SELECT P.SSN
FROM Person P, inserted I
WHERE P.SSN = I.SSN))
INSERT INTO Person
SELECT SSN,Name,Address,Birthdate
FROM inserted
ELSE
-- Log an attempt to insert duplicate Person row in PersonDuplicates table.
INSERT INTO PersonDuplicates
SELECT SSN,Name,Address,Birthdate,SUSER_SNAME(),GETDATE()
FROM inserted
-- Check for duplicate Employee. If no there is duplicate, do an INSERT.
IF (NOT EXISTS (SELECT E.SSN
FROM EmployeeTable E, inserted
WHERE E.SSN = inserted.SSN))
INSERT INTO EmployeeTable
SELECT EmployeeID,SSN, Department, Salary
FROM inserted
ELSE
--If there is a duplicate, change to UPDATE so that there will not
--be a duplicate key violation error.
UPDATE EmployeeTable
SET EmployeeID = I.EmployeeID,
Department = I.Department,
Salary = I.Salary
FROM EmployeeTable E, inserted I
WHERE E.SSN = I.SSN
END
Changes to trigger above to only insert unique rows
CREATE TRIGGER IO_Trig_INS_TEST ON MyTestTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
IF (
NOT EXISTS ( SELECT P.SSN
FROM MyTestTable P
,inserted I
WHERE P.SSN = I.SSN ) )
INSERT INTO MyTestTable
SELECT FirstName
,LastName
,SSN
FROM ( SELECT FirstName
,LastName
,SSN
,ROW_NUMBER() OVER ( PARTITION BY SSN ORDER BY LastName, Firstname ) AS ROWNUM
FROM INSERTED ) A
WHERE A.ROWNUM = 1
ELSE
RAISERROR(N'You are trying to insert duplicate records',16,1)
END

After insert, update timestamp trigger with two column primary key

I have a simple details table like so:
listid
custid
status
last_changed
The primary key consists of both listid and custid.
Now I'm trying to setup a trigger that sets the last_changed column to the current datetime every time an insert or update happens. I've found lots of info on how to do that with a single PK column, but with multiple PKs it gets confusing on how to correctly specify the PKs from the INSERTED table.
The trigger has to work in SQL Server 2005/2008/R2.
Thanks for a working trigger code!
Bonus would be to also check if the data was actually altered and only update last_changed in that case but for the sake of actually understanding how to correctly code the main question I'd like to see this as a separate code block if at all.
Hmm.... just because the primary key is made up of two columns shouldn't really make a big difference....
CREATE TRIGGER dbo.trgAfterUpdate ON dbo.YourTable
AFTER INSERT, UPDATE
AS
UPDATE dbo.YourTable
SET last_changed = GETDATE()
FROM Inserted i
WHERE dbo.YourTable.listid = i.listid AND dbo.YourTable.custid = i.custid
You just need to establish the JOIN between the two tables (your own data table and the Inserted pseudo table) on both columns...
Are am I missing something?? .....
CREATE TRIGGER dbo.trgAfterUpdate ON dbo.YourTable
AFTER INSERT, UPDATE
AS
UPDATE dbo.YourTable
SET last_changed = GETDATE()
FROM Inserted i
JOIN dbo.YourTable.listid = i.listid AND dbo.YourTable.custid = i.custid
WHERE NOT EXISTS
(SELECT 1 FROM Deleted D Where D.listid=I.listid AND D.custid=i.custid AND (D.status=i.status)
Here i assuming that stasus column is not nullable. If yes, you should add additional code to check if one of columns is NULL
You can check every field in trigger by comparing data from inserted and deleted table like below :
CREATE TRIGGER [dbo].[tr_test] ON [dbo].[table]
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE #old_listid INT
DECLARE #old_custid INT
DECLARE #old_status INT
DECLARE #new_listid INT
DECLARE #new_custid INT
DECLARE #new_status INT
SELECT #old_listid=[listid], #old_custid=[custid], #old_status = [status] FROM [deleted]
SELECT #new_listid=[listid], #new_custid=[custid], #new_status = [status] FROM [inserted]
IF #oldstatus <> #new_status
BEGIN
UPDATE TABLE table SET last_changed = GETDATE() WHERE [listid] = #new_listid AND [custid] = #new_custid
END
END

Can a SQL Server 2000 table have no PK, and therefore contain duplicate records?

I have an audit table and instead of defining an identity or ticketed column, I'm considering just pushing in the records of the recorded table (via triggers).
Can a SQL Server 2000 table have no PK, and therefore contain duplicate records?
If yes, does all I have to do consist of CREATING the TABLE without defining any constraint on it?
Yes, this is possible, but not necessarily a good idea. Replication and efficient indexing will be quite difficult without a primary key.
Yes a table without a primary key or Unique Constraint can have rows that are duplicated
for example
CREATE TABLE bla(ID INT)
INSERT bla (ID) VALUES(1)
INSERT bla (ID) VALUES(1)
INSERT bla (ID) VALUES(1)
SELECT * FROM bla
GO
Yes a SQL Server 2000 table can have no primary key and contain duplicate records and yes you can simply Create a table without defining any constraint on it. However I would not suggest this.
Instead, since you are creating an audit table for another table. Lets say for this example you have a Person Table and a Person Audit table that tracks changes in the person Table.
Create your Audit Table like this
CREATE TABLE dbo.PersonAuditID
(
PersonAuditID int NOT NULL IDENTITY (1, 1),
PersonId int NOT NULL,
FirstName nvarchar(50) NOT NULL,
LastName nvarchar(50) NOT NULL,
PersonWhoMadeTheChange nvarchar(100) NOT NULL,
TimeOfChange datetime NOT NULL,
ChangeAction int NOT NULL,
/* any other fields here*/
CONSTRAINT [PK_PersonAudit] PRIMARY KEY NONCLUSTERED
(
[PersonAuditID] ASC
)
) ON [PRIMARY]
This will give you a primary key, and keep records unique to the table. It also provides the ability to track who made the change, when the change was made, and if the change was an insert, update or delete.
Your triggers would look like the following
CREATE TRIGGER Insert_PERSON
ON PERSON
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO PERSONAUDIT
(PersonID,
FirstName,
LastName,
PersonWhoMadeTheChange,
TimeOfChange,
ChangeAction,
... other fields here
SELECT
PersonID,
FirstName,
LastName,
User(),
getDate(),
1,
... other fields here
FROM INSERTED
END
CREATE TRIGGER Update_PERSON
ON PERSON
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO PERSONAUDIT
(PersonID,
FirstName,
LastName,
PersonWhoMadeTheChange,
TimeOfChange,
ChangeAction,
... other fields here
SELECT
PersonID,
FirstName,
LastName,
User(),
getDate(),
2,
... other fields here
FROM INSERTED
END
CREATE TRIGGER Delete_PERSON
ON PERSON
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO PERSONAUDIT
(PersonID,
FirstName,
LastName,
PersonWhoMadeTheChange,
TimeOfChange,
ChangeAction,
... other fields here
SELECT
PersonID,
FirstName,
LastName,
User(),
getDate(),
3,
... other fields here
FROM DELETED
END
SQL Server 2000+, can have tables without PK. And yes, you create them by no using a constraint.
For an audit table, you need to think of what you may be using the audit data for. And even if you are not doing auditing to spefically use to restore records when unfortunate changes were made, they are inevitably used for this. Will it be easier to identify the record you want to restore if you have a surrogate key that prevents you from accidentally restoring 30 other entries when you only want the most recent? Will a key value help you identify the 32,578 records that were deleted in one batch that needs to be restored?
What we do for auditing is have two tables for each table, one stores information about the batch of records changed, including an auto-incrementing id, the user, the application, the datetime, the number of affected records. The child table then used the ID as the fk and stored the details about the old and new values for each record inserted/updated/deleted. This really helps us when a process bug causes many records to be changed by accident.

SQL Server 2005 How can I set up an audit table that records the column name updated?

given this table definition
create table herb.app (appId int identity primary key
, application varchar(15) unique
, customerName varchar(35),LoanProtectionInsurance bit
, State varchar(3),Address varchar(50),LoanAmt money
,addedBy varchar(7) not null,AddedDt smalldatetime default getdate())
I believe changes will be minimal, usually only a single field, and very sparse.
So I created this table:
create table herb.appAudit(appAuditId int primary key
, field varchar(20), oldValue varchar(50),ChangedBy varchar(7) not null,AddedDt smalldatetime default getdate())
How in a trigger can I get the column name of the value of what was changed to store it? I know how to get the value by joining the deleted table.
Use the inserted and deleted tables. Nigel Rivett wrote a great generic audit trail trigger using these tables. It is fairly complex SQL code, but it highlights some pretty cool ways of pulling together the information and once you understand them you can create a custom solution using his ideas as inspiration, or you could just use his script.
Here are the important ideas about the tables:
On an insert, inserted holds the inserted values and deleted is empty.
On an update, inserted holds the new values and deleted holds the old values.
On a delete, deleted holds the deleted values and inserted is empty.
The structure of the inserted and deleted tables (if not empty) are identical to the target table.
You can determine the column names from system tables and iterate on them as illustrated in Nigel's code.
if exists (select * from inserted)
if exists (select * from deleted)
-- this is an update
...
else
-- this is an insert
...
else
-- this is a delete
...
-- For updates to a specific field
SELECT d.[MyField] AS OldValue, i.[MyField] AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.[MyPrimaryKeyField] = d.[MyPrimaryKeyField]
-- For your table
SELECT d.CustomerName AS OldValue, i.CustomerName AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.appId = d.appId
If you really need this kind of auditing in a way that's critical to your business look at SQL Server 2008's Change Data Capture feature. That feature alone could justify the cost of an upgrade.
something like this for each field you want to track
if UPDATE(Track_ID)
begin
insert into [log].DataChanges
(
dcColumnName,
dcID,
dcDataBefore,
dcDataAfter,
dcDateChanged,
dcUser,
dcTableName
)
select
'Track_ID',
d.Data_ID,
coalesce(d.Track_ID,-666),
coalesce(i.Track_ID,-666),
getdate(),
#user,
#table
from inserted i
join deleted d on i.Data_ID=d.Data_ID
and coalesce(d.Track_ID,-666)<>coalesce(i.Track_ID,-666)
end
'Track_ID' is the name of the field, and d.Data_ID is the primary key of the table your tracking. #user is the user making the changes, and #table would be the table your keeping track of changes in case you're tracking more than one table in the same log table
Here's my quick and dirty audit table solution. (from http://freachable.net/2010/09/29/QuickAndDirtySQLAuditTable.aspx)
CREATE TABLE audit(
[on] datetime not null default getutcdate(),
[by] varchar(255) not null default system_user+','+AppName(),
was xml null,
[is] xml null
)
CREATE TRIGGER mytable_audit ON mytable for insert, update, delete as
INSERT audit(was,[is]) values(
(select * from deleted as [mytable] for xml auto,type),
(select * from inserted as [mytable] for xml auto,type)
)

Resources