How to optimize a trigger? - sql

CREATE TRIGGER T
ON TABLE_2
AFTER INSERT
AS
DECLARE #bought_t int,
#name_t varchar(20)
SELECT #name_t = name_t
FROM inserted
SELECT #bought_t = bought_t
FROM TABLE_1
WHERE name_t = #name_t
IF #bought_t < 100
BEGIN
UPDATE TABLE_1
SET bought_t = #bought_t + 1
WHERE TABLE_1.name_t = #name_t
END
ELSE
ROLLBACK TRANSACTION
The column (TABLE_1) I'm making the update to after the insert happens in the 'TABLE_2' is supposed to hold values between 50 and 100. So I'm asking If this trigger is as professional and optimized as It could be? or I have some flaws that could lead to bugs/security issues.

Basically, you need to completely rewrite your trigger to be set-based and to be able to work with multiple rows in the Inserted pseudo table.
Fortunately, that also makes it easier - in my opinion - try something like this:
CREATE TRIGGER T
ON TABLE_2
AFTER INSERT
AS
UPDATE T1
SET bought_t = bought_t + 1
FROM TABLE_1 T1
INNER JOIN Inserted i ON i.name_t = T1.name_t
WHERE T1.bought_t < 100
UPDATE: demo to prove this works:
-- create the two tables
CREATE TABLE TABLE_2 (ID INT NOT NULL IDENTITY(1,1), ProdName VARCHAR(50))
CREATE TABLE TABLE_1 (ProdName VARCHAR(50), Bought INT)
GO
-- create trigger on "TABLE_2" to update "TABLE_1"
CREATE TRIGGER T2Insert
ON TABLE_2
AFTER INSERT
AS
UPDATE T1
SET Bought = Bought + 1
FROM TABLE_1 T1
INNER JOIN Inserted i ON T1.ProdName = i.ProdName
WHERE T1.Bought < 100
GO
-- initialize TABLE_1 with some seed data
INSERT INTO dbo.TABLE_1 (ProdName, Bought)
VALUES ( 'Prod1', 0), ('Prod2', 20), ('Prod3', 40), ('Prod4', 40), ('Prod100', 100)
-- insert new values into TABLE_2
INSERT INTO dbo.TABLE_2 (ProdName)
VALUES ('Prod1'), ('Prod100'), ('Prod2'), ('Prod4')
-- get data to check
SELECT * FROM dbo.TABLE_1
This renders output:
As you can easily see:
Prod1, Prod2, Prod4 that were inserted caused an update of the value Bought
Prod100 which was also inserted did not cause an update of Bought
UPDATE #2: if you need to be able to insert multiple identical values at once, you need to slightly enhance your trigger like this:
CREATE TRIGGER T2Insert
ON TABLE_2
AFTER INSERT
AS
-- declare table variable to hold names and update counts
DECLARE #UpdateCount TABLE (Name VARCHAR(50), UpdCount INT)
-- from the "Inserted" table, determine which names are being
-- inserted how many times using GROUP BY
INSERT INTO #UpdateCount (Name, UpdCount)
SELECT ProdName, COUNT(*)
FROM Inserted
GROUP BY ProdName
-- now join to this temporary table, and update as many times
-- as needed (instead of +1 for all cases)
UPDATE T1
SET Bought = Bought + uc.UpdCount
FROM TABLE_1 T1
INNER JOIN #UpdateCount uc ON uc.Name = T1.ProdName
WHERE T1.Bought < 100
GO

Related

Insert or update multiples rows

I have two tables where TableA has latest data and TableB has some old data. I want to update TableB's data if it matches id with TableA and if doesn't match insert new row in TableB.
I got a solution from stackOverflow
begin tran
if exists (select * from t with (updlock,serializable) where pk = #id)
begin
update t set hitCount = hitCount+1
where pk = #id
end
else
begin
insert t (pk, hitCount)
values (#id, 1)
end
commit tran
But it seems I need to pass #id each time, may be I am not getting it in the correct way. I have hundreds of row to update/insert from tableA.
Think relationally.
SQL Server always operates sets. A single row is just a set of 1 row.
Here is a simple example of two step update - insert operations
create table #tableA(id int, [year] int, updated_value int)
insert #tableA(id,[year],updated_value)
values
(1,1990,85),
(2,1991,70),
(3,1992,80)
create table #tableB(id int, [year] int, score int)
insert #tableB(id,[year],score)
values
(1,1990,50),
(4,1995,20)
update #tableA set
updated_value=b.score
from #tableA a
inner join #tableB b on a.id=b.id --inner is important
insert #tableA(id,[year],updated_value)
select b.id,b.[year],b.score
from #tableB b
left join #tableA a on a.id=b.id --left is important
where a.id is null -- and this line too
select * from #tableA
If you wish you can combine update and insert in a single merge operation.
merge #tableA as tgt
using #tableB as src
on src.id=tgt.id
when matched then
update set updated_value=src.score
when not matched then
insert(id,[year],updated_value)
values(id,[year],score)
; -- semicoloumn is required
select * from #tableA

How to write a check to avoid the message "INSERT statement conflicted with the FOREIGN KEY constraint"?

I read and understood the entries in following asked question: INSERT statement conflicted with the FOREIGN KEY constraint
. I do get the point, however, I'm in this situation where I have around 1 Gb of records need to be inserted into a table, some of those records have conflicted foreign key. The query looks like this:
IF NOT EXISTS (SELECT * FROM [dbo].[tbl_R_TaskHistory] WHERE [TaskID] =
10000529)
BEGIN insert into [dbo].[tbl_History]
([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
values (10000529,'A0000187',NULL,5738366,0,NULL,CAST(N'2011-03-16
04:53:37.210' AS DateTime)) END
The conflict ocurs on RequestID, so I was thinking there must be a way to make a check to avoid the error messages.
My point is that I want my query to check if the RequestID has not FOREIGN KEY constraint it will not insert this record and move to the next one.
If your query contains only one row, you can just expand the check like this:
IF NOT EXISTS (SELECT * FROM [dbo].[tbl_R_TaskHistory] WHERE [TaskID] = 10000529) AND EXISTS(SELECT 1 FROM [dbo].[...referencing table...] WHERE [RequestD] = 5738366)
BEGIN
insert into [dbo].[tbl_History] ([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
values (10000529,'A0000187',NULL,5738366,0,NULL,CAST(N'2011-03-16 04:53:37.210' AS DateTime));
END
Anyway, if you are inserting many rows at the same time and for performance considerations, it will be better to store the values in buffer table. Something like this:
insert into #tbl_History ([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
values (10000529,'A0000187',NULL,5738366,0,NULL,CAST(N'2011-03-16 04:53:37.210' AS DateTime))
,(...)
,(...)
,(...)
Then, just perform an inner join to your referencing table:
insert into [dbo].[tbl_History] ([TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed])
SELECT [TaskID],[UserID],[ActD],[RequestD],[No],[SignID],[Completed]
FROM #tbl_History A
INNER JOIN [dbo].[...referencing table...] B
ON A.[RequestD] = B.[RequestD];
This syntax also works
declare #a int = 5;
declare #b int = 18;
insert into sample (a, b)
select #a, #b
where not exists (select 1 from sample where b = #b)
and exists (select 1 from student where iden = #a)
This avoids creating a #temp
insert into sample (a, b)
select a, b
from ( values (5,19)
, (5,30)
, (5,31)
, (5,32)
, (7,41)
, (7,42)
) v(a,b)
where not exists (select 1 from sample where b = v.b)
and exists (select 1 from student where iden = v.a)

Joining multiple table Sql trigger

Hi I am newbie to SQL trigger. since I tried and searched on online and I dont find any clear outcome.
so here is my problem.
I have three tables:
TABLE1 :
ID NAME (columns )
1 prabhu
TABLE2 :
Id COUNTRY (columns )
1 India
I want this to send to log table if anything like insert/update happen in table2
The SQL(DB2) trigger has to do the following and the result should be in log table like this
LOGTABLE:
ID NAME COUNTRY
1 prabhu India
Your help really appreciated.
Try this,
-- Create tables
create table table1(id int, empName varchar(20));
create table table2(id int, country varchar(20));
create table logtable(id int, empName varchar(20), country varchar(20));
-- Create trigger
CREATE TRIGGER logtableAfterInsert ON table2
after INSERT,DELETE,UPDATE
AS
BEGIN
declare #empid int;
declare #empname2 varchar(20);
declare #empcountry varchar(20);
select #empid=i.id from inserted i;
select #empcountry=i.country from inserted i;
select #empname2=tbl1.empName from table1 tbl1 where tbl1.id=#empid;
insert into logtable values(#empid,#empname2,#empcountry);
PRINT 'Inserted'
END
GO
After that insert the values,
insert into table1 values(1, 'prabhu');
insert into table2 values (1, 'India');
Check the results,
select * from table1;
select * from table2;
select * from logtable;
Hope this resolves...
BTW, You need to add the foreign key constraint.
CREATE OR REPLACE TRIGGER logtableAfterUpdate
AFTER UPDATE ON table2
REFERENCING NEW AS NAUDIT OLD AS OAUDIT
FOR EACH ROW MODE DB2SQL
--BEGIN --ATOMIC
insert into logtable
values(
(select id from table2 tbl2 where tbl2.id =OAUDIT.id),
(select empName from table1 tbl1 where tbl1.id=(select id from table2 tbl2 where tbl2.id =OAUDIT.id)),
(select country from table2 tbl2 where tbl2.id =OAUDIT.id)
);
--END;

How to Delete the Existing data based on Where clause condition using Merge

i have written a merge Statement where i am facing trouble to delete the data basing on Where Clause Condition.
Let me explain my scenario Clearly
For example i have inserted Data from Source to Target based on Date Key Condition.Take an Instance 10 Records Inserted.
For example some changes in the records and it has been updated through the Merge Statement .
For the Same Date key based on Conditions now three records has came and need to be inserted and rest should be deleted for that Date Key.
How i need to proceed on this before 10 records are not getting deleted and new records adding for that one
My Example Code :
DELETE FROM #Table1
CREATE TABLE #Table1
(ID INT ,Name VARCHAR(30),DATEKEY INT)
INSERT INTO #Table1 (ID,Name,DATEKEY)VALUES (1,'Mohan',20131231)
INSERT INTO #Table1 (ID,Name,DATEKEY)VALUES (2,'Raj',20131231)
INSERT INTO #Table1 (ID,Name,DATEKEY)VALUES (3,'Majjaa',20131231)
INSERT INTO #Table1 (ID,Name,DATEKEY)VALUES (4,'Majjaa',20131231)
CREATE TABLE #Table2
(ID INT ,Name VARCHAR(30),DATEKEY INT)
DECLARE #i_DateKey INT
SET #i_DateKey = '20131231'
MERGE #Table2 AS T
USING (
SELECT pdp.ID
,pdp.Name
,pdp.DATEKEY
FROM #Table1 AS pdp
WHERE (
pdp.DateKey = #i_DateKey
OR #i_DateKey IS NULL
)
) AS S
ON T.ID = S.ID
AND T.DateKey = S.DateKey
AND T.NAME = S.NAME
WHEN MATCHED
THEN
UPDATE
SET T.NAME = S.NAME
WHEN NOT MATCHED BY TARGET
THEN
INSERT
VALUES (
S.ID
,S.Name
,S.DateKey
)
WHEN NOT MATCHED BY SOURCE
THEN
DELETE ;
Now the target table will be loaded with Rows now if i send the another row for Same Date key then it need to be deleted all the 4 rows and reload the new Row if the new row is same then need to update
i think this is one big mistake,
ON T.ID = S.ID --i am not sure about this
AND T.DateKey = S.DateKey
AND T.NAME = S.NAME -- remove this because this when matched then update will do what ?
WHEN MATCHED
THEN
UPDATE
SET T.NAME = S.NAME

How do I get temp values to be set after an insert has occured in a trigger?

I have a trigger I am working on that will insert rows into a table when another table has inserts or updates applied to it. So far the Update portion works (the column that I'm most concerned with is the Balance column), but when the first row is added for an insert on the Account table, in my AuditTrailCustomerBalance table OldBalance, NewBalance and CustNo are set to NULL. How can I get NewBalance and CustNo to reference to the values that were just inserted into the table from the trigger?
Here is the trigger:
ALTER TRIGGER AuditTrigger
ON Accounts
FOR INSERT, UPDATE
AS
IF UPDATE( Balance )
BEGIN
IF EXISTS
(
SELECT 'True'
FROM Inserted i
JOIN Deleted d
ON i.AccountID = d.AccountID
)
BEGIN
--1. Declare temp variables.
DECLARE #OldBalance NUMERIC( 18, 0 )
DECLARE #NewBalance NUMERIC( 18, 0 )
DECLARE #CustNo INT
--2. Set the variables.
SELECT #OldBalance = Balance FROM deleted
SELECT #NewBalance = Balance FROM inserted
SELECT #CustNo = CustNo FROM inserted
INSERT INTO AuditTrailCustomerBalance( TimeChanged, ChangedBy, OldBalance, NewBalance, CustNo )
VALUES( GETDATE(), SUSER_SNAME(), #OldBalance, #NewBalance, #CustNo )
END
END
GO
And the test statement:
INSERT INTO Custs( CustNo, GivenName, Surname, DOB, SIN )
VALUES( 1, 'Peter', 'Griffen', 'January 15, 1950', '555555555')
INSERT INTO Accounts( CustNo, Type, Balance, AccruedInt, WithdrawalCount )
VALUES( 1, 'Savings', 0, 0, 0 )
UPDATE Accounts SET Balance = 100
WHERE CustNo = 1
I believe that you want something like this:
ALTER TRIGGER AuditTrigger
ON Accounts
FOR INSERT, UPDATE
AS
INSERT INTO AuditTrailCustomerBalance(TimeChanged, ChangedBy,
OldBalance, NewBalance, CustNo )
SELECT GETDATE(), SUSER_SNAME(),
COALESCE(d.Balance,0), i.Balance, i.CustNo
FROM inserted i
left join
deleted d
on
i.AccountNo = d.AccountNo
WHERE
i.Balance <> d.Balance OR
d.Balance IS NULL
As I said in my comments, inserted and deleted can contain multiple rows (or no rows) and so you need to take that into account and write a set-based query that deals with all of those rows - also some rows may have had balance changes and some not - so deciding whether to write any entries based on UPDATE(Balance) was also flawed.
you can if you are sure of your code write something like this :
if (select count(*) from inserted) = 1
and execute your code.
You can for the insert do like this :
insert into AuditTrailCustomerBalance (.....)
select .... from inserted
as already posted, the problem with your trigger is in the calling if you update one row or multiple (same for insert)