How to logs all rows affected after delete trigger on multiple rows - sql-server

I have table QuoteDetail with multiple rows with the same Quotation Number.
The records can be deleted using bulk delete 'Delete from QuoteDetail Where QuoteNo ='12345'
How can I logs all deleted rows using after delete trigger?
Below is my trigger script but only logs 1 row and the rest are discarded.
CREATE TRIGGER [dbo].[trgcsms_QuoteDetail_DeleteLogs] ON [CSMS].[dbo].[QuoteDetail]
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #QuoteNo varchar(50)
DECLARE #SeqNo int
DECLARE #ItemCode varchar(50)
DECLARE #Qty float
DECLARE #NetPrice float
DECLARE #TotAmt float
SELECT #QuoteNo = DELETED.[QuoteNo]
,#SeqNo = DELETED.[SeqNo]
,#ItemCode = DELETED.[ItemCode]
,#Qty = DELETED.[Qty]
,#TotAmt = DELETED.[TotAmt]
FROM DELETED
INSERT INTO [dbo].[QuoteDetailDeleteLogs]
(
[QuoteNo]
,[SeqNo]
,[ItemCode]
,[Qty]
,[TotAmt]
)
VALUES
(
#QuoteNo
,#SeqNo
,#ItemCode
,#Qty
,#NetPrice
,#TotAmt
)
END

You almost got it righ. Just not using "#variables" and instead using tables. Because after all, you want multiple rows, right? That's also known as "a table".
Also worth noting - I took the liberty of upgrading your trigger to include UPDATE as well, because an update is a DELETE joined with an immediate INSERT afterwards. So I imagine if you update a line and QuoteNo (quote number) changed from '12345' to '54321' you would want that in your audit trail (your "QuoteDetailsLog").
[updated 30Aug2018 ]
CREATE TRIGGER [dbo].[trgcsms_QuoteDetail_DeleteLogs] ON [CSMS].[dbo].
[QuoteDetail]
AFTER DELETE, UPDATE
AS
BEGIN
INSERT INTO [dbo].[QuoteDetailDeleteLogs]
(
[QuoteNo]
,[SeqNo]
,[ItemCode]
,[Qty]
,[TotAmt]
)
SELECT [QuoteNo]
,[SeqNo]
,[ItemCode]
,[Qty]
,[TotAmt]
FROM DELETED
END
keeping it short and simple

Related

Insert and update multiple records via same stored procedure

I created this stored procedure to go through all the records in the table comparing the id (primary key) if exists and the records changed, make the necessary changes & update the record.
If the id is not in the table then insert the record. This stored procedure
compiles fine, but doesn't seem to work properly. Does this need a while loop?
ALTER PROCEDURE [dbo].[SMLineUpdate]
(
#id [int],
#Payroll_Id [int],
#ProductCode nvarchar(255),
#Description nvarchar (255),
#Qty nvarchar(255)
)
AS
IF EXISTS (SELECT Id from Smline where #id = Id) BEGIN
update dbo.SmLine
Set [Payroll_Id] = #Payroll_Id
, ProductCode = #ProductCode
, Description = #Description
, Qty = #Qty
END ELSE BEGIN
INSERT INTO SmLine ([Payroll_Id], [ProductCode], [Description], [Qty])
VALUES (#Payroll_Id, #ProductCode, #Description, #Qty)
END
Your update query is missing a where condition
update dbo.SmLine
Set [Payroll_Id] = #Payroll_Id
,ProductCode = #ProductCode
,Description = #Description
,Qty = #Qty
WHERE Id = #Id -- the query missed this where condition
IF EXISTS(SELECT Id from Smline where Id =#id)
BEGIN
update dbo.SmLine
Set [Payroll_Id]= #Payroll_Id
,ProductCode= #ProductCode
,Description = #Description
,Qty = #Qty
WHERE Id = #Id
END
ELSE
BEGIN
INSERT INTO SmLine ([Payroll_Id],[ProductCode],[Description],[Qty])
VALUES (#Payroll_Id,#ProductCode ,#Description,#Qty)
END
Your SP does not meet the requirement of insert multiple records. It works only for a single record update or inserts, you have to pass multiple id's and values respectively for update multiple so use a different approach like XML as an input parameter so u can simply do this operation for multiple by extracting the XML data.
Your update statement lacks a where statement. That is a major 'no-no', as it will (god forbid...) update all lines in the table.
Your insert statement lacks an identity insert, so consider the case where you are trying to update/insert id=5, but by now this line is deleted (not found in the where), and ids are much bigger. you would search for it -- > not find, and insert a new line (say id=101), then look for id=5 again, not find it again, and insert it again (say id=102), and so on... I don't think that's what you intended. Consider a Merge statement (when matched/when not matched) and get the best of both worlds. Also consider not deleting from the table, and instead add an 'IsDeleted' column (which allows 'reviving' a deleted row).

SQL Server INSERT in an AFTER UPDATE Trigger

I'm new to SQL Server, and I'm trying to build a simple update trigger that writes a row to a staging table whenever the column ceu_amount is updated from zero to any number greater than zero.
From using PRINT statements, I know that the variables are containing the correct values to execute the INSERT statement, but no rows are being inserted.
Can you help?
CREATE TRIGGER [dbo].[TRG_Product_Function_Modified] ON [dbo].[Product_Function]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
--
-- Variable definitions
--
DECLARE #product_code_new as varchar(31)
DECLARE #product_code_old as varchar(31)
--
-- Check if the staging table needs to be updated.
--
SELECT #product_code_new = product_code FROM Inserted where ISNULL(ceu_amount,0) > 0;
SELECT #product_code_old = product_code FROM Deleted where ISNULL(ceu_amount,0) = 0;
IF #product_code_new IS NOT NULL
AND #product_code_old IS NOT NULL
INSERT INTO Product_Function_Staging VALUES (#product_code_new,CURRENT_TIMESTAMP);
END;
This part of code looks suspicious to me..
SELECT #product_code_new = product_code FROM Inserted where ISNULL(ceu_amount,0) > 0;
SELECT #product_code_old = product_code FROM Deleted where ISNULL(ceu_amount,0) = 0;
IF #product_code_new IS NOT NULL
AND #product_code_old IS NOT NULL
INSERT INTO Product_Function_Staging VALUES (#product_code_new,CURRENT_TIMESTAMP);
The above will work fine ,if there is only one row updated,what if there is more than one value..the product_code will default to last value
You can change the above part of code to below
Insert into Product_Function_Staging
select product_code ,CURRENT_TIMESTAMP from inserted where product_code is not null
You will get undetermined values for #product_code_new if there are more than one rows updated with ceu_amount>0; Similar for #product_code_old if more than one rows updated with ceu_amount NULL or equal 0.
Can you post some sample data?
I would not use variables like that in a trigger, since what causes the trigger could be an update to more than one row, at which point you would have multiple rows in your updated and deleted tables.
I think we can more safely and efficiently make this insert with one simple query, though I'm assuming you have a unique key to use:
CREATE TRIGGER [dbo].[TRG_Product_Function_Modified] ON [dbo].[Product_Function]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO Product_Function_Staging
SELECT i.product_code, CURRENT_TIMESTAMP
FROM inserted i
JOIN deleted d ON i.product_code = d.product_code -- assuming product_code is unique
WHERE i.ceu_amount > 0 -- new value > 0
AND ISNULL(d.ceu_amount, 0) = 0; -- old value null or 0
END;
I'm not sure where you need to check for nulls in your data, so I've made a best guess in the where clause.
Try using this
CREATE TRIGGER [dbo].[Customer_UPDATE]
ON [dbo].[Customers]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CustomerId INT
DECLARE #Action VARCHAR(50)
SELECT #CustomerId = INSERTED.CustomerId
FROM INSERTED
IF UPDATE(Name)
BEGIN
SET #Action = 'Updated Name'
END
IF UPDATE(Country)
BEGIN
SET #Action = 'Updated Country'
END
INSERT INTO CustomerLogs
VALUES(#CustomerId, #Action)
END

Create Transaction and Trigger on a field

Please am new in vb.net and sql server, I have created a two tables in database called Service and Trans.
Create Table Service(
ServiceID int,
ServiceName varchar(30),
ServiceStartValue int
);
Create Table Trans(
EntryTS datetime,
EntryCounter int,
ServedTS datetime,
ServedCounter int,
Skipped int
);
I am trying to create a 'transaction and trigger' that will check and update ServedCounter based on the values in EntryCounter upon ServiceID which the update statement must not allow the ServedCounter > EntryCounter.
I don't quite understand the full requirement, but here's how you can prevent a update (or insert) from happening with a trigger.
CREATE TRIGGER Trans_upd_trg ON Trans AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
--Don't allow the update
IF EXISTS(SELECT 1 FROM inserted WHERE ServedCounter > EntryCounter)
RAISERROR ('ServedCounter > EntryCounter', 16, 1 );
END
GO
Within the context of the trigger you have two logical tables, INSERTED, and DELETED.
These tables contain the old and new values. (deleted is empty for a insert operation)
Hope that helps.
Use a Instead Of Trigger
CREATE TRIGGER Trans_upd_trg
ON Trans
Instead OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT *
FROM inserted
WHERE ServedCounter > EntryCounter)
AND EXISTS (SELECT *
FROM deleted)
UPDATE A
SET EntryTS = I.EntryTS,
EntryCounter = I.EntryCounter,
ServedTS = I.ServedTS,
ServedCounter = I.ServedCounter,
Skipped = I.Skipped
FROM Trans A
JOIN inserted I
ON A.EntryTS = I.EntryTS
AND A.ServedTS = I.ServedTS
WHERE i.ServedCounter > i.EntryCounter
ELSE
INSERT INTO Trans
SELECT *
FROM inserted
WHERE ServedCounter > EntryCounter
END
GO

Update the same table in the trigger

I have a table which calculate the total deduction and the remaining deduction from the previous month.
And any update in the remaining deduction of old month should affect the next months in order, so I've created an AFTER UPDATE trigger for this table, but I've noticed that the trigger is not invoked by the UPDATE for the next month.
Check the trigger code
CREATE TRIGGER [dbo].[tgUpdateRemainingBalance]
ON [dbo].[GAT_MONTHLY_DEDUCTION]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #OldTotalDeduction INT
DECLARE #NewTotalDeduction INT
DECLARE #OldRemainingToNextMonth INT
DECLARE #NewRemainingToNextMonth INT
SELECT #OldRemainingToNextMonth = RemainingToNextMonth,
#OldTotalDeduction = TotalDeduction
FROM DELETED
SELECT #NewRemainingToNextMonth = RemainingToNextMonth,
#NewTotalDeduction = TotalDeduction
FROM INSERTED
DECLARE #EmpNo BIGINT
DECLARE #RegDate DATETIME
SELECT #EmpNo = PersonnelNo, #RegDate = RegDate FROM INSERTED
IF (#OldRemainingToNextMonth <> #NewRemainingToNextMonth) OR (#OldTotalDeduction <> #NewTotalDeduction)
BEGIN
UPDATE GAT_MONTHLY_DEDUCTION
SET TotalDeduction = TotalDeduction - #OldRemainingToNextMonth + #NewRemainingToNextMonth,
Visited = 1
WHERE RegDate = DATEADD(month, 1, #RegDate) AND PersonnelNo = #EmpNo
END
END
I am doing updating for the same table which has this triggers but calling the trigger for the first time should call the trigger again for the next update but it is not happening.
It is urgent for me to know how to solve it.
Thanks
One thing to watch out for: the SQL Server trigger is fired once per statement (and NOT once per row as many developer think/assume)
If that statement updates more than one row, then the Inserted and Deleted pseudo tables will contain multiple rows - so your selects like
SELECT
#NewRemainingToNextMonth = RemainingToNextMonth,
#NewTotalDeduction = TotalDeduction
FROM INSERTED
will fail - which row should be selected, if there are fifty of them in Inserted??
You will need to rewrite your trigger to be able to deal with multiple rows being handled at once, in the Inserted and Deleted pseudo tables...

SQL Server - Auto-incrementation that allows UPDATE statements

When adding an item in my database, I need it to auto-determine the value for the field DisplayOrder. Identity (auto-increment) would be an ideal solution, but I need to be able to programmatically change (UPDATE) the values of the DisplayOrder column, and Identity doesn't seem to allow that. For the moment, I use this code:
CREATE PROCEDURE [dbo].[AddItem]
AS
DECLARE #DisplayOrder INT
SET #DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1
INSERT INTO [dbo].[MyTable] ( DisplayOrder ) VALUES ( #DisplayOrder )
Is it the good way to do it or is there a better/simpler way?
A solution to this issue from "Inside Microsoft SQL Server 2008: T-SQL Querying"
CREATE TABLE dbo.Sequence(
val int IDENTITY (10000, 1) /*Seed this at whatever your current max value is*/
)
GO
CREATE PROC dbo.GetSequence
#val AS int OUTPUT
AS
BEGIN TRAN
SAVE TRAN S1
INSERT INTO dbo.Sequence DEFAULT VALUES
SET #val=SCOPE_IDENTITY()
ROLLBACK TRAN S1 /*Rolls back just as far as the save point to prevent the
sequence table filling up. The id allocated won't be reused*/
COMMIT TRAN
Or another alternative from the same book that allocates ranges easier. (You would need to consider whether to call this from inside or outside your transaction - inside would block other concurrent transactions until the first one commits)
CREATE TABLE dbo.Sequence2(
val int
)
GO
INSERT INTO dbo.Sequence2 VALUES(10000);
GO
CREATE PROC dbo.GetSequence2
#val AS int OUTPUT,
#n as int =1
AS
UPDATE dbo.Sequence2
SET #val = val = val + #n;
SET #val = #val - #n + 1;
You can set your incrementing column to use the identity property. Then, in processes that need to insert values into the column you can use the SET IDENITY_INSERT command in your batch.
For inserts where you want to use the identity property, you exclude the identity column from the list of columns in your insert statement:
INSERT INTO [dbo].[MyTable] ( MyData ) VALUES ( #MyData )
When you want to insert rows where you are providing the value for the identity column, use the following:
SET IDENTITY_INSERT MyTable ON
INSERT INTO [dbo].[MyTable] ( DisplayOrder, MyData )
VALUES ( #DisplayOrder, #MyData )
SET IDENTITY_INSERT MyTable OFF
You should be able to UPDATE the column without any other steps.
You may also want to look into the DBCC CHECKIDENT command. This command will set your next identity value. If you are inserting rows where the next identity value might not be appropriate, you can use the command to set a new value.
DECLARE #DisplayOrder INT
SET #DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1
DBCC CHECKIDENT (MyTable, RESEED, #DisplayOrder)
Here's the solution that I kept:
CREATE PROCEDURE [dbo].[AddItem]
AS
DECLARE #DisplayOrder INT
BEGIN TRANSACTION
SET #DisplayOrder = (SELECT ISNULL(MAX(DisplayOrder), 0) FROM [dbo].[MyTable]) + 1
INSERT INTO [dbo].[MyTable] ( DisplayOrder ) VALUES ( #DisplayOrder )
COMMIT TRANSACTION
One thing you should do is to add commands so that your procedure's run as a transaction, otherwise two inserts running at the same time could produce two rows with the same value in DisplayOrder.
This is easy enough to achieve: add
begin transaction
at the start of your procedure, and
commit transaction
at the end.
You way works fine (with a little modification) and is simple. I would wrap it in a transaction like #David Knell said. This would result in code like:
CREATE PROCEDURE [dbo].[AddItem]
AS
DECLARE #DisplayOrder INT
BEGIN TRANSACTION
SET #DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1
INSERT INTO [dbo].[MyTable] ( DisplayOrder ) VALUES ( #DisplayOrder )
COMMIT TRANSACTION
Wrapping your SELECT & INSERT in a transaction guarantees that your DisplayOrder values won't be duplicated by AddItem. If you are doing a lot of simultaneous adding (many per second), there may be contention on MyTable but for occasional inserts it won't be a problem.

Resources