SQL trigger - after insert,update wrong and correct values - sql-server

I have 3 tables:
transaction(IDtransaction,IDperson,IDPos,IDStore,TotalValue,Date)
items(IDtransaction,IDItem,IDproduct,Quantity)
persons(IDperson, balance)
What I am trying to accomplish is to delete from transaction when the total value is bigger that the balance of that person however it doesn't seem to work when I insert at the same time a wrong and a correct record. Why isn't working?
create trigger trigger4
on transaction
After insert,update
As
BEGIN
delete transaction
FROM inserted i
where transaction.IDtransaction=i.IDtransaction AND EXISTS (select * from inserted i, persons p
where i.IDperson=p.IDperson
and i.TotalValue > p.balance)
if ##ROWCOUNT > 0
begin
print('Error!')
if exists(select 1 from deleted) and exists (select 1 from inserted) --check if it is an update
begin
SET IDENTITY_INSERT transaction ON
insert into transaction(IDtransaction,IDperson,IDPos,IDStore,TotalValue,Date)
select d.IDtransaction,d.IDperson,d.IDPos,d.IDStore,d.TotalValue,d.Date
from deleted d
where d.IDtransaction not in (select t.IDtransaction from transaction t)
SET IDENTITY_INSERT transaction OFF
end
end
END

I'm a little surprised that this doesn't throw a syntax error, but this bit is wrong:
delete transaction
FROM inserted i
where transaction.IDtransaction=i.IDtransaction AND EXISTS (select * from inserted i, persons p
where i.IDperson=p.IDperson
and i.TotalValue > p.balance)
Because you aren't joining inserted to transaction so the DELETE isn't correlated to the FROM.

Related

Trigger is not fired for all rows in case of update(random)

I have created a single trigger to catch all insert, update and delete operations in a replica table but in case of update, two rows are inserted one with before update values and another row with after update values but it is not working as expected.
I have tried all possible ways to do it. Now, I want to know whether I should use transaction at trigger level for update operation or should try something else.
ALTER TRIGGER [dbo].[trgAfterInsertUpdateDelete_xyz] ON [dbo].[xyz]
FOR UPDATE,INSERT, DELETE
AS
declare #accountID int;
declare #billingDate date;
declare #amount decimal(18, 2);
---- Get data from inserted/ updated
select #accountID = i.AccountID from inserted i;
select #billingDate=i.BillingDate from inserted i;
select #amount=i.Amount from inserted i;
-- Insert Case
IF EXISTS( SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted)
BEGIN
insert into xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
values(#accountID,#billingDate,#amount,'INSERT');
END
-- Update Case
IF EXISTS( SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted)
BEGIN
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
SELECT d.AccountID, d.BillingDate, d.Amount,
'BeforeUpdate' FROM Inserted i
INNER JOIN Deleted d ON i.ID = d.ID
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount,Audit_Action)
values(#accountID,#billingDate,#amount,'AfterUpdate');
END
-- Delete Case
IF EXISTS( SELECT * FROM deleted) AND NOT EXISTS(SELECT * FROM inserted)
BEGIN
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
select accountID,billingDate,amount, 'DELETE'
from deleted
END
I expected that if 9 records are updated then there should be before and after update values for all rows but sometimes it skips. As you can see for Inst.8 there should be before and after rows but it has captured only before row and then insert but in actual there was no row deleted and inserted, only updation was done. Sometime out of 9, it pick only before rows not after update rows or sometime 2 or 3 after update rows not before update rows.
your trigger assuming that only one row will be affected for insert / update / delete operatons. Which means the inserted or deleted may contains multiple rows
and the following part does not handle that.
---- Get data from inserted/ updated
select #accountID = i.AccountID from inserted i;
select #billingDate=i.BillingDate from inserted i;
select #amount=i.Amount from inserted i;
Actually you don't required the above at all. You should just insert into the Audit table directly from the inserted or deleted table
For example the "INSERT CASE" should be
-- Insert Case
IF EXISTS( SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted)
BEGIN
insert into xyz_Audit (AccountID, BillingDate, Amount, Audit_Action)
select AccountID, BillingDate, Amount, Audit_Action= 'INSERT'
from inserted;
END
similarly you need to change accordingly for the Update section. And you are already doing it correctly for the delete section

Why deleting the rows using merge command is taking more time than delete statement in SQL Server 2008 R2

This is my scenario: I have a table TableA from which I have to archive the data into TableB. That is I have to delete the rows from TableA and insert the deleted rows into TableB for a given modifieddate (e.g. 2015-01-01).
Now as the number of data rows to be deleted is 2 million, I have to delete the rows in chunks so as to minimize locking.
Out of the two methods I observed that simple delete statement was far better than the merge statement. Delete statement took 15 seconds to delete from TableA and insert the deleted records into TableB. However it took 7 min to complete the same task using the merge statement. Can someone tell me why ?
For reference please see my code below : -
-----------Merge statement--------------
declare #i int = 1
while #i > 0
begin
begin try
begin transaction
merge top (1000) tableA as a
using tableB as b on a.id = b.id
when not matched by source and a.modifieddate = '2015-01-01'
then
delete
output deleted.id, deleted.column1, deleted.column2 into TableB(id, column1, column2);
set #i = ##rowcount
if ##trancount > 0
commit transaction
end try
begin catch
if ##trancount > 0
rollback transaction
end catch
end
and now the delete statement:
----------------Delete statement--------
declare #i int = 1
while #i > 0
begin
begin transaction
delete top (1000) a
output deleted.id, deleted.column1, deleted.column2 into TableB(id, column1, column2)
from tableA as a
where a.modifieddate = '2015-01-01'
set #i = ##rowcount
if ##trancount > 0
commit transaction
end
I have proper indexes on the column. Columnd Id has clustered index and column1, column2 have nonclustered indexes for TableA and TableB both.
Can someone please tell me what's wrong in my Merge statement and why it is taking so much more time?
Thanks in advance

SQL insert trigger to catch NULL values for mutiple rows

I have written the trigger below that prevents from NULL being entered in the pch_x field . It works fine if i insert 1 row but doesnt work if I enter more than one at once . Could someone please help me out a little ? Here is my code
create trigger test
ON [dbo].TEMP
for INSERT
AS
BEGIN
declare #xcheck varchar(50)
set #xcheck= (select i.pch_x FROM temp L INNER JOIN INSERTED I
ON L.id = I.id)
F (#xcheck is NULL )
begin
RAISERROR('NULL in pch_x', 16, 1)
ROLLBACK
end
END
I'm not sure why you're doing this in a trigger, but the set based way to do this test would be to use EXISTS:
create trigger test
ON [dbo].TEMP
for INSERT
AS
BEGIN
IF EXISTS(select * FROM temp L INNER JOIN
INSERTED I
ON L.id = I.id
where i.pch_x IS NULL)
begin
RAISERROR('NULL in pch_x', 16, 1)
ROLLBACK
end
END
I'm also not sure why you're joining back to the table - I'd have thought the check could run without reference to temp:
create trigger test
ON [dbo].TEMP
for INSERT
AS
BEGIN
IF EXISTS(select * FROM INSERTED
where pch_x IS NULL)
begin
RAISERROR('NULL in pch_x', 16, 1)
ROLLBACK
end
END
For you unusual requirement that, in a rowset containing some rows with nulls, you want success for those rows without nulls and failure for those rows with nulls, most sensible would be an INSTEAD OF trigger:
create trigger test
ON [dbo].TEMP
INSTEAD OF INSERT
AS
BEGIN
declare #rc int
INSERT INTO dbo.temp (/* column list */)
SELECT /* column list */ from inserted where pch_x IS NOT NULL
set #rc = ##ROWCOUNT
IF #rc <> (select COUNT(*) from inserted)
begin
RAISERROR('NULL in pch_x', 16, 1)
--ROLLBACK
end
END

Handling multiple rows in SQL Server trigger

We have a database with a table called WarehouseItem where product's stock levels are kept. I need to know when ever this table get's updated, so I created a trigger to put the primary key of this table row that got updated; into a separate table (like a queue system).
This is my trigger:
IF ((SELECT COUNT(*) FROM sys.triggers WHERE name = 'IC_StockUpdate') > 0)
DROP TRIGGER [dbo].[IC_StockUpdate]
GO
CREATE TRIGGER [dbo].[IC_StockUpdate] ON [dbo].[WarehouseItem]
AFTER UPDATE
AS
BEGIN
-- Get Product Id
DECLARE #StockItemID INT = (SELECT ItemID FROM INSERTED);
DECLARE #WarehouseID INT = (SELECT WarehouseID FROM INSERTED);
-- Proceed If This Product Is Syncable
IF (dbo.IC_CanSyncProduct(#StockItemID) = 1)
BEGIN
-- Proceed If This Warehouse Is Syncable
IF (dbo.IC_CanSyncStock(#WarehouseID) = 1)
BEGIN
-- Check If Product Is Synced
IF ((SELECT COUNT(*) FROM IC_ProductCreateQueue WHERE StockItemID = #StockItemID) > 0)
BEGIN
-- Check If Stock Update Queue Entry Already Exists
IF ((SELECT COUNT(*) FROM IC_StockUpdateQueue WHERE StockItemID = #StockItemID) > 0)
BEGIN
-- Reset [StockUpdate] Queue Entry
UPDATE IC_StockUpdateQueue SET Synced = 0
WHERE StockItemID = #StockItemID;
END
ELSE
BEGIN
-- Insert [StockUpdate] Queue Entry
INSERT INTO IC_StockUpdateQueue (StockItemID, Synced) VALUES
(#StockItemID, 0);
END
END
ELSE
BEGIN
-- Insert [ProductCreate] Queue Entry
INSERT INTO IC_ProductCreateQueue (StockItemID, Synced) VALUES
(#StockItemID, 0);
-- Insert [StockUpdate] Queue Entry
INSERT INTO IC_StockUpdateQueue (StockItemID, Synced) VALUES
(#StockItemID, 0);
END
END
END
END
GO
This works perfectly fine, if only a single row is updated in the "WarehouseItem" table.
However, if more than one row is updated in this table, my trigger is failing to handle it:
Is there a way to iterate through the "inserted" collection after a mass update event? Or how does one handle multiple row updates in trigger?
You use this:
-- Get Product Id
DECLARE #StockItemID INT = (SELECT ItemID FROM INSERTED);
DECLARE #WarehouseID INT = (SELECT WarehouseID FROM INSERTED);
But if you update multi rows (as your sample) you must use a different strategy.
For example, instead to declare a variable, use INSERTED table in JOIN in query where now you use your variable.
IF statement works on your variable but I think to move that condition in query.
Try to change you UPDATE query in this way (eventually add condition of IF):
-- Reset [StockUpdate] Queue Entry
UPDATE IC_StockUpdateQueue SET Synced = 0
FROM inserted
WHERE inserted.itemID = StockItemID;
And so on.
For further information please add comment.
You could use a loop to iterate over INSERTED but it may be better to change your scalar variables into a TABLE and INSERT-SELECT from INSERTED where the IDs meet the criteria of the first two IFs
DECLARE #inserted TABLE (StockItemID INT, WarehouseID INT)
INSERT INTO #inserted (StockItemID, WarehouseID)
SELECT StockItemID, WarehouseID
FROM INSERTED i
WHERE dbo.IC_CanSyncProduct(i.StockItemID)=1
AND dbo.IC_CanSyncStock(i.WarehouseID)=1
then you can remove the if else upsert logic and use queries that further filter #inserted for the various updates and inserts that are required
;WITH ResetQueueEntry
(
SELECT StockItemID
FROM #inserted i
WHERE EXISTS(SELECT 1 FROM IC_ProductCreateQueue q WHERE q.StockItemID = i.StockItemID)
AND EXISTS(SELECT 1 FROM IC_StockUpdateQueue q WHERE q.StockItemID = i.StockItemID))
)
-- Reset [StockUpdate] Queue Entry
UPDATE IC_StockUpdateQueue
SET Synced = 0
WHERE StockItemID IN (SELECT StockItemID FROM ResetStockUpdate);
WITH InsertQueueEntry
(
SELECT StockItemId, 0 Synced
FROM #inserted
WHERE EXISTS(SELECT 1 FROM IC_ProductCreateQueue q WHERE q.StockItemID = i.StockItemID)
AND NOT EXISTS(SELECT 1 FROM IC_StockUpdateQueue q WHERE q.StockItemID = i.StockItemID))
)
-- Insert [StockUpdate] Queue Entry
INSERT INTO IC_StockUpdateQueue (StockItemID, Synced)
SELECT StockItemID, Synced
FROM InsertQueueEntry
WITH CreateProductEntry
(
SELECT StockItemId, 0 Synced
FROM #inserted
WHERE NOT EXISTS(SELECT 1 FROM IC_ProductCreateQueue q WHERE q.StockItemID = i.StockItemID)
)
-- Insert [ProductCreate] Queue Entry
INSERT INTO IC_ProductCreateQueue (StockItemID, Synced)
SELECT StockItemId, Synced
FROM CreateProductEntry
WITH CreateStockEntry
(
SELECT StockItemId, 0 Synced
FROM #inserted
WHERE NOT EXISTS(SELECT 1 FROM IC_ProductCreateQueue q WHERE q.StockItemID = i.StockItemID)
)
-- Insert [StockUpdate] Queue Entry
INSERT INTO IC_StockUpdateQueue (StockItemID, Synced)
SELECT StockItemId, Synced
FROM CreateProductEntry
in case of the trigger is for INSERT, UPDATE
this code will exit the trigger IF Records are being updated AND more than one record is being afftected:
IF (SELECT COUNT(*) FROM Deleted) > 1
BEGIN
Return
END
But if you wish to examin every record in the INSERTED recordset you can use this method:
DECLARE rstAST CURSOR FOR
SELECT ins.TaskActionId,
_Task.CustomerId,
_AST.ASTQRId,
ins.ExistingQRcode,
ins.NewQRcode
FROM Inserted ins INNER JOIN
dbo.cdn_AST _AST ON ins.ASTId = _AST.ASTId INNER JOIN
dbo.tsk_Task _Task ON ins.TaskId = _Task.TaskId
OPEN rstAST
FETCH NEXT FROM rstAST INTO #TaskActionId, #TaskCustomerId, #ASTQRId, #ExistingQRcode, #NewQRcode
WHILE ##FETCH_STATUS = 0
BEGIN
--use CONTINUE to skip next record or let it traverse the loop
FETCH NEXT FROM rstAST INTO #TaskActionId, #TaskCustomerId, #ASTQRId, #ExistingQRcode, #NewQRcode
END
CLOSE rstAST
DEALLOCATE rstAST

How to copy an inserted,updated,deleted row in a SQL Server trigger(s)

If a user changes table HelloWorlds, then I want 'action they did', time they did it, and a copy of the original row insert into HelloWorldsHistory.
I would prefer to avoid a separate triggers for insert, update, and delete actions due to the column lengths.
I've tried this:
create trigger [HelloWorlds_After_IUD] on [HelloWorlds]
FOR insert, update, delete
as
if ##rowcount = 0
return
if exists (select 1 from inserted) and not exists (select 1 from deleted)
begin
insert into HelloWorldHistory (hwh_action, ..long column list..)
select 'INSERT', helloWorld.id, helloWorld.text ... and more from inserted
end
else
if exists (select 1 from inserted) and exists (select 1 from deleted)
begin
insert into HelloWorldHistory (hwh_action, ..long column list..)
select 'UPDATE', helloWorld.id, helloWorld.text ... and more from deleted
end
else
begin
insert into HelloWorldHistory (hwh_action, ..long column list..)
select 'DELETE', helloWorld.id, helloWorld.text ... and more from deleted
end
end
I've never seen an insert appear, but I've seen updates. I'm going to try 3 separate triggers, though maintaining the column lists will not be fun.
try something like this:
CREATE TRIGGER YourTrigger ON YourTable
AFTER INSERT,UPDATE,DELETE
AS
DECLARE #HistoryType char(1) --"I"=insert, "U"=update, "D"=delete
SET #HistoryType=NULL
IF EXISTS (SELECT * FROM INSERTED)
BEGIN
IF EXISTS (SELECT * FROM DELETED)
BEGIN
--UPDATE
SET #HistoryType='U'
END
ELSE
BEGIN
--INSERT
SET #HistoryType='I'
END
--handle insert or update data
INSERT INTO YourLog
(ActionType,ActionDate,.....)
SELECT
#HistoryType,GETDATE(),.....
FROM INSERTED
END
ELSE IF EXISTS(SELECT * FROM DELETED)
BEGIN
--DELETE
SET #HistoryType='D'
--handle delete data, insert into both the history and the log tables
INSERT INTO YourLog
(ActionType,ActionDate,.....)
SELECT
#HistoryType,GETDATE(),.....
FROM DELETED
END
--ELSE
--BEGIN
-- both INSERTED and DELETED are empty, no rows affected
--END
You need to associate (match) the rows in the inserted and deleted columns. Something like this should work better.
create trigger [HelloWorlds_After_IUD] on [HelloWorlds]
FOR insert, update, delete
as
insert into HeloWorldsHistory
select 'INSERT', helloWorld.id, helloWorld.text ... and more
from inserted
where myKeyColumn not in (select myKeyColumn from deleted)
insert into HeloWorldsHistory
select 'DELETE', helloWorld.id, helloWorld.text ... and more
from deleted
where myKeyColumn not in (select myKeyColumn from inserted)
insert into HeloWorldsHistory
select 'UPDATE', helloWorld.id, helloWorld.text ... and more
from inserted
where myKeyColumn in (select myKeyColumn from deleted)

Resources