After Update Trigger Insert old records to another table - sql-server

I have MedicalAllowance Table i want to make vesrion from this table as history table "MedicalAllowanceHistory" i want to create trigger to do that after update
MedicalAllowance Table copy data to "MedicalAllowanceHistory" table .which way is better to do that .
MedicalAllowance table
ID | Employee_ID | Limit | Used | Balance
MedicalAllowanceHistory table
ID | Employee_ID | Limit | Used | Balance

you can use a trigger for that
But I would also store the date of the event, and the kind of event (insert, update or delete)
Here is an example to get you started
CREATE trigger tr_UID_MedicalAllowance on dbo.MedicalAllowance
after update, insert, delete
as
begin
set nocount on
declare #Insert bit = 0
declare #Update bit = 0
declare #Delete bit = 0
--find out why we where triggered
if (exists(select 1 from inserted)) and (exists(select 1 from deleted))
set #Update = 1
else if (exists(select 1 from inserted))
set #Insert = 1
else if (exists (select 1 from deleted))
set #Delete = 1
if #Update = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
other columns...)
select i.MedicalAllowanceID,
getdate(),
'UPDATED',
i.other columns...
from inserted i
end
if #Insert = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
other columns...)
select i.MedicalAllowanceID,
getdate(),
'INSERTED',
i.other columns...
from inserted i
end
if #Delete = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
other columns...)
select d.MedicalAllowanceID,
getdate(),
'DELETED',
d.other columns...
from deleted d
end
end
It is also possible to store old and new values in case of update, the new values are in the inserted table and the old are in the deleted table.
In that case the update part could look something like this
if #Update = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
NewLimit,
OldLimit,
other columns...)
select i.MedicalAllowanceID,
getdate(),
'UPDATED',
i.Limit,
d.Limit,
other columns...
from inserted i
inner join deleted d on i.MedicalAllowanceID = d.MedicalAllowanceID
end

I suggest you to use "Change Data Capture" instead of "Trigger". In this solution SQL tracks any changes. If you have need to learn more about it,click here.

You can use the following example to solve your problem:
CREATE TRIGGER tg_MedicalAllowance ON MedicalAllowance
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
INSERT MedicalAllowanceHistory
SELECT ID,Employee_ID,Limit,Used,balance
FROM deleted
END

Related

How can I update the rows that are existed before the insert using Trigger ( SQL Server )?

I'm looking for a method to update old rows before an insert or update using a Trigger ,
For example , I have this table
ID PersonID Name Status
1 001 Alex False
2 002 Mark True
What I need exactly is that when I insert in this table a new row (3,003,Jane,True) , the column status should be affected to False ( all old rows ) only the new row will have True
So the expected result when applying the trigger will be like this :
ID PersonID Name Status
1 001 Alex False
2 002 Mark False
3 003 Jane True
How can I do this?
What I have tried:
ALTER TRIGGER [dbo].[dbo.TR_SetStatus] ON [dbo].[Person]
after INSERT
NOT FOR REPLICATION
AS
DECLARE #CursorTestID INT = 1;
DECLARE #RowCnt BIGINT = 0;
BEGIN
DECLARE #count INT;
SELECT #RowCnt = COUNT(*) FROM Person;
WHILE #CursorTestID <= #RowCnt
BEGIN
update Person set status=0
SET #CursorTestID = #CursorTestID + 1
END
END
I have two questions:
How can I update the rows that are existed before the insert using Trigger ( SQL Server )?
How can I pass a parameter to a trigger? (as an example PersonID)
DROP TABLE IF EXISTS dbo.Test;
CREATE TABLE dbo.Test
(
id tinyint identity(1,1)not null primary key,
person_id char(3)not null,
name varchar(50)not null,
status varchar(5) not null
)
insert dbo.Test(person_id,name,status)
values('001','alex','false'),('002','mark','true');
go
SELECT *FROM DBO.Test
DROP TRIGGER IF EXISTS dbo.II_Test;
GO
CREATE TRIGGER dbo.II_Test
ON dbo.Test
INSTEAD OF INSERT
AS
BEGIN
UPDATE DBO.Test SET status='FALSE';
INSERT DBO.Test(person_id,name,status)
SELECT I.person_id,I.name,I.status
FROM inserted AS I
END
GO
insert dbo.Test(person_id,name,status)
values('003','JANE','true');
select * from dbo.Test
Could you please check if the above is suitable for you
How can I update the rows that are existed before the insert using Trigger ( SQL Server )
For example, you can use INSTEAD OF-trigger
How can I pass a parameter to a trigger? (as an example PersonID)
This is not supported at all. If you need parameters the better way, I guess, is to use stored procedure
Finally, I solved my problem ( the first question ) :
ALTER TRIGGER [dbo].[dbo.TR_SetActive] ON [dbo].[test]
after INSERT
NOT FOR REPLICATION
AS
BEGIN
update dbo.test set status=0 WHERE Id < (SELECT MAX(Id) FROM dbo.test)
END
For the second question , I have used to get the last record as parameter:
set #PersonId = (select PersonId from inserted)
Can be simplfy with :
CREATE TRIGGER dbo.E_I_Test
ON dbo.Test
FOR INSERT
AS
BEGIN
UPDATE T
SET status = CASE WHEN I.id IS NULL THEN 0 ELSE 1 END
FROM dbo.Test as T
LEFT OUTER JOIN inserted AS I
ON T.id = I.id;
BEWARE.... status is a reserved Transact SQL keyword. Should not be use for any SQL identifier (table, name, column neme...)
Corrections made...

SQL Server trigger only if a field is false prior to update

I have written the trigger below that inserts values if the emailstudio column is updated. This column can be 0 or 1.
How can I write the trigger so that it only fires if the emailstudio column is changed from 0 to 1, not if it was already 1?
Thank you
ALTER TRIGGER [dbo].[RM_Est_Email_Trigger]
ON [dbo].[K3_BriefHeader]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #estimate int, #Email_Date datetime, #status int, #emailstudio bit
SET #estimate = (SELECT Estimate from inserted)
set #Email_Date = GETDATE()
SET #status = (SELECT Status from inserted)
SET #emailstudio = (SELECT EmailStudio from inserted)
IF UPDATE (EmailStudio)
BEGIN
INSERT INTO [dbo].[K3_EstimateEmailDate] ([Estimate], [Email_Date],[Status], [EmailStudio])
VALUES (#estimate, #Email_Date, #status, #emailstudio)
END
END
Insert INTO [dbo].[K3_EstimateEmailDate] (
[Estimate]
,[Email_Date]
,[Status]
,[EmailStudio]
)
SELECT Estimate
,GETDATE()
,status
,1
FROM inserted
LEFT JOIN deleted
ON deleted.<primarykey> = inserted.<primarykey>
WHERE inserted.emailstudio = 1
AND (deleted.emailstudio is null -- inserted
OR deleted.emailstudio = 0) -- updated

How can I update a column in SQL Server using a trigger

I am trying to create a trigger within SQL Server Management Studio that will increment a column value by 1 when a separate column has been updated within the same table.
The value for the column we want to update when the update script has been ran becomes NULL
My example is that I when I change the address of a customer, I want a column that goes up by 1 every time the address is changed i.e NoOfAddressess = 1, 2, 3 etc...
Here is the SQL code that I am writing
ALTER TRIGGER trg_customeraudit
ON tblCustomer
AFTER UPDATE, DELETE, INSERT
AS
INSERT INTO dbo.CustomerDetailsAudit
VALUES (CURRENT_USER, CURRENT_TIMESTAMP,
(SELECT CustomerID FROM inserted),
(SELECT CustomerAddress FROM deleted),
(SELECT CustomerAddress FROM inserted),
(SELECT CustomerPostcode FROM deleted),
(SELECT CustomerPostcode FROM inserted),
(SELECT NumberOfChangedAddresses FROM dbo.CustomerDetailsAudit)
)
IF ((SELECT CustomerAddress FROM inserted) =
(SELECT CustomerAddress FROM deleted) OR
(SELECT CustomerPostcode FROM deleted) =
(SELECT CustomerPostcode FROM inserted))
BEGIN
RAISERROR ('You must enter both a new postcode and address',16,10)
ROLLBACK TRANSACTION
END
ELSE
BEGIN
PRINT 'Transaction successful'
WHERE CustomerID = (SELECT CustomerID from inserted)
END
IF UPDATE (CustomerName)
BEGIN
RAISERROR ('You cannot change the customer name', 16, 10)
ROLLBACK
END
Depending on the other things happening on this data triggers can be a very inefficient method of handling this, but here is one possible solution.
1. Setup
First create a table to use for testing.
create table test_table (
MyPrimaryKey int primary key clustered not null identity(1, 1)
, SomeColumn varchar(255) not null
, SomeColumnCounter int null
);
go
Now, add a trigger to initialize the counter to 1. This could be handled by a default constraint or set at the application level but it can also be done with a trigger.
-- this trigger will set the counter to 1 when a record is first added
-- doesn't need to be a trigger, but since the question was on triggers
create trigger trg_test_table_insert
on test_table
after insert
as
update tt
set tt.SomeColumnCounter = 1
from
test_table as tt
inner join
Inserted as i
on
tt.MyPrimaryKey = i.MyPrimaryKey;
go
Now, add a trigger that will check for changes on the designated column and increment the counter if needed.
-- this trigger will increment the counter by 1 if 'SomeColumn' changed
-- doesn't handle nulls so will need to be modified depending on schema
create trigger trg_test_table_update
on test_table
after update
as
update tt
set tt.SomeColumnCounter = tt.SomeColumnCounter + 1
from
Inserted as i -- new version of the record
inner join
Deleted as d -- old version of the record
on
i.MyPrimaryKey = d.MyPrimaryKey
and i.SomeColumn <> d.SomeColumn
inner join
test_table as tt
on
tt.MyPrimaryKey = i.MyPrimaryKey
go
2. Testing
Add some test data.
insert into test_table (SomeColumn)
values ('abc'), ('def');
go
Now we have:
MyPrimaryKey SomeColumn SomeColumnCounter
1 abc 1
2 def 1
Update without changing anything:
update tt
set tt.SomeColumn = 'abc'
from
test_table as tt
where
tt.MyPrimaryKey = 1
We still have:
MyPrimaryKey SomeColumn SomeColumnCounter
1 abc 1
2 def 1
Update that actually changes something:
update tt
set tt.SomeColumn = 'abbc'
from
test_table as tt
where
tt.MyPrimaryKey = 1
Now we have:
MyPrimaryKey SomeColumn SomeColumnCounter
1 abbc 2
2 def 1
Update that changes everything:
update tt
set tt.SomeColumn = tt.SomeColumn + 'z'
from
test_table as tt
Now we have:
MyPrimaryKey SomeColumn SomeColumnCounter
1 abbcz 3
2 defz 2

Converting SQL Server script to Oracle

I have a script that I created that works fine in SQL Server, but now I need to make it work in Oracle and I am having issues getting the script converted.
Here is my SQL Server Script:
-- run the commented out statement in another query analyzer window to stop the script
-- update ##stopsign set val = 1
set nocount on
--declare variables
declare #morework int
declare #archivecount int
declare #stopsign int
--if working tables exists clear them, if not create and initialize them
if (object_id('tempdb..##stopsign') is null)
create table ##stopsign (val int)
else
delete from ##stopsign
insert into ##stopsign values (0)
if (object_id('tempdb..#tempdins') is null)
create table #tempdins (tempdin varchar(255) not null, processed int null)
else
delete from #tempdins
--initialize #tempdins working table with all the records eligible to be unarchived
--edit the select statement if needed to change the records to be unarchived
insert into #tempdins(tempdin)
select tempdin
from document
where archivestatus = 'C'
and status = 'U'
option (MAXDOP 1)
--inialize variables with current values
select #archivecount = (select count(*) from unarchs)
select #stopsign = (select val from ##stopsign)
select #morework = 1
--while there is more to do, unarchs table has less then 1000 records, and the stopsign value is 0 loop
while (#morework >= 1 and #stopsign = 0)
begin
if #archivecount <1000
begin
-- number to be processed at once
-- change this value if you would like to dump more in to the unarchs table at once
set rowcount 100
update #tempdins
set processed = 0
where processed is null
--reset rowcount
set rowcount 0
--populate the unarchs table with valid values
--this will unarchive at the page (lowest) level
insert into unarchs (drawer,foldernumber,packageid,docid,pagenumber,unarchtype,unarchdate,unarchtime,userid,unarchdays)
select distinct drawer,foldernumber,packageid,docid,pagenumber,'Page','20061128','12:00:00','ADMIN',360
from document
where tempdin in (select tempdin
from #tempdins
where processed = 0)
--update with rowcount to see if finished
select #morework = ##rowcount
--set the tempdins to processed in working table
update #tempdins
set processed = 1
where processed = 0
--get new counts for variables for evaulation
select #archivecount = (select count(*) from unarchs)
select #stopsign = (select val from ##stopsign)
--wait a second so the CPU doesn't spin
waitfor delay '00:00:01'
end
else
begin
--get new counts for variables for evaulation
select #archivecount = (select count(*) from unarchs)
select #stopsign = (select val from ##stopsign)
--wait a second so the CPU doesn't spin
waitfor delay '00:00:01'
end
end
set nocount off
Here is what I have for ORACLE so far (writing in PL/SQL):
-- run the commented out statement in another query analyzer window to stop the script
-- update ##stopsign set val = 1
--if working tables exists clear them, if not create and initialize them
declare
v_sql LONG;
begin
v_sql:='CREATE GLOBAL TEMPORARY TABLE STOPSIGN;
(
VAL int
)';
execute immediate v_sql;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -955 THEN
NULL; -- suppresses ORA-00955 exception
ELSE
delete from STOPSIGN;
END IF;
END;
/
insert into STOPSIGN values (0);
--if working tables exists clear them, if not create and initialize them
declare
v_sql LONG;
begin
v_sql:='CREATE GLOBAL TEMPORARY TABLE TEMPDINS;
(
tempdin varch(255),
processed int null
)';
execute immediate v_sql;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -955 THEN
NULL; -- suppresses ORA-00955 exception
ELSE
delete from TEMPDINS;
END IF;
END;
/
--initialize #tempdins working table with all the records eligible to be unarchived
--edit the select statement if needed to change the records to be unarchived
insert into TEMPDINS(tempdin)
select * from (select tempdin
from document join packtype on packtype.packagetype=document.packagetype
and archivestatus = 'C' and ADRIVE is not null
and ADRIVE <> DRIVE ) where ROWNUM < 10;
--inialize variables with current values
Declare
archivecount int;
stopsign int;
morework int;
Begin
Select count(*) INTO archivecount from UNARCHS;
Select VAL into stopsign from STOPSIGN;
morework := 1;
END
--while there is more to do, unarchs table has less then 1000 records, and the stopsign value is 0 loop
WHILE morework > 0 and stopsign = 0
LOOP{
begin
if archivecount <1000
begin
-- number to be processed at once
-- change this value if you would like to dump more in to the unarchs table at once
set rowcount 100
update TEMPDINS
set processed = 0
where processed is null
}
--reset rowcount
set rowcount 0
--populate the unarchs table with valid values
--this will unarchive at the page (lowest) level
insert into UNARCHS (drawer,foldernumber,packageid,docid,pagenumber,unarchtype,unarchdate,unarchtime,userid,unarchdays)
select distinct drawer,foldernumber,packageid,docid,pagenumber,'Page','20061128','12:00:00','ADMIN',360
from DOCUMENT
where tempdin in (select tempdin
from TEMPDINS
where processed = 0)
--update with rowcount to see if finished
select morework = select NUM_ROWS into morework from user_tables where table_name = 'UNARCHS'
--set the tempdins to processed in working table
update TEMPDINS
set processed = 1
where processed = 0
--get new counts for variables for evaulation
select archivecount = (select count(*) from unarchs)
select stopsign = (select val from STOPSIGN)
--wait a second so the CPU doesn't spin
waitfor delay '00:00:01'
end
else
begin
--get new counts for variables for evaulation
select archivecount = (select count(*) from unarchs)
select stopsign = (select val from STOPSIGN)
--wait a second so the CPU doesn't spin
waitfor delay '00:00:01'
end
end
End
END IF
END LOOP
Any help would be appreciated. My company has no Oracle resources for me to go to and Google is getting tired of me.
I think you can get rid of everything and just do a single insert statement:
insert into unarchs (drawer,foldernumber,packageid,docid,pagenumber,unarchtype,unarchdate,unarchtime,userid,unarchdays)
select distinct drawer,foldernumber,packageid,docid,pagenumber,'Page','20061128','12:00:00','ADMIN',360
from DOCUMENT
where archivestatus = 'C'
And status = 'U';
I’m assuming you have a different process that updates the rows in the document table so this process doesn’t constantly pick up the same rows?

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.

Resources