SQL Server 2014, trigger : how to iterate over deleted rows? - sql-server

ATTENTION! Using trigger is necessary for me! The only way needed for me to perform following task - is to use trigger!
The question is - I need a trigger, which would be called on rowS deletion in a certain table. The thing trigger must do - iterate over every deleted
row, and perform some code - depending on current iterated row values.
for example, I made this trigger, which works when I delete one row
by command
DELETE FROM books WHERE id=5
the trigger is here:
GO
CREATE TRIGGER onBookDelete ON books
INSTEAD OF DELETE
AS
DECLARE #book_id int
BEGIN
SET NOCOUNT ON;
SELECT id FROM deleted
SET #book_id = (SELECT id FROM deleted)
PRINT #book_id
BEGIN
DELETE FROM "books-topics" WHERE book_id=#book_id
DELETE FROM "books-genres" WHERE book_id=#book_id
DELETE FROM books WHERE id=#book_id
END
END;
So, what I want to achieve - is to create trigger with same task,
but it must work when I delete many rows at one time, for example
DELETE FROM books WHERE id=5 OR id=3 OR id=8
by same task I mean, for example, calling the following code on each deleted row
DELETE FROM "books-topics" WHERE id=DELETEDROW.book_id
DELETE FROM "books-genres" WHERE id=DELETEDROW.book_id
DELETE FROM books WHERE id=DELETEDROW.book_id

Your teacher should have thought about a better example for an instead of delete trigger. This problem is what on delete cascade is there for in the first place.
However, since your assignment is to use a trigger, I would suggest something like this instead:
CREATE TRIGGER onBookDelete ON books
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRASACTION
BEGIN TRY
DELETE bt
FROM "books-topics" bt
INNER JOIN deleted d ON bt.book_id= d.book_id
DELETE bg
FROM "books-genres" bg
INNER JOIN deleted d ON bg.book_id= d.book_id
DELETE b
FROM books b
INNER JOIN deleted d ON b.book_id= d.book_id
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH
END;

Related

t-sql INSTEAD OF deleting a row PRINT its id

Is it possible to make such a trigger that runs when you try to delete a row and prints something like this: "Attempting to delete row ROW_ID", instead of actually deleting the row?
UPD: Thanks. Worked for me:
GO
CREATE OR ALTER TRIGGER Trigger_2 ON Authors
INSTEAD OF DELETE
AS BEGIN
DECLARE #deleted_id INT;
DECLARE cursor_deleted CURSOR
FOR SELECT au_id FROM deleted;
OPEN cursor_deleted;
FETCH NEXT FROM cursor_deleted INTO #deleted_id;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT('Attempting to delete author ' + STR(#deleted_id));
FETCH NEXT FROM cursor_deleted INTO #deleted_id;
END;
CLOSE cursor_deleted;
DEALLOCATE cursor_deleted;
END;
GO
DELETE FROM Authors WHERE au_id BETWEEN 1 AND 10;
Yeah it is, and there are many approaches.
using a trigger you can either set the trigger to run after a deletion and u will have to use "ROLLBACK" to undo the deletion operation.
or just simply use INSTEAD OF where as the name suggests the trigger will replace the who deletion operation.
hope this helps in any way.
ps: after a deletion operation deleted data are stored in a Global table named "deleted" use to get any data or metaData that u need.
CREATE TRIGGER schema_name.trigger_name
ON table_name
// this trigger is executed instead of any deletion
INSTEAD OF DELETE
AS
BEGIN
// return the one to be deleted's id.
return deleted.id
END

"Instead of delete trigger" triggers two times

I am using MS SQL Server 2016 where I have implemented a instead of delete trigger. It looks like this:
ALTER TRIGGER MyTrigger ON MyTable INSTEAD OF DELETE AS
BEGIN
IF --some condition
BEGIN
RAISERROR ('Error msg', 16, 1)
ROLLBACK TRAN
RETURN
END
DELETE MyTable FROM MyTable JOIN deleted ON MyTable.id = deleted.id
END
If I execute a DELETE statement on the table 'MyTable' and the condition in the if is not fulfilled the DELETE statement is executed after the if-block. This is absolutely correct. But in the console of SSMS it is written twice that the DELETE statement was executed. So the following is written in the console:
(1 rows affected)
(1 rows affected)
I do not understand why. Why does SSMS indicate twice that a row is affected? I use SSMS version 15.0.18338.0.
This is because there were 2 sets of data effect, the set outside the TRIGGER, and then again inside it, because the initial dataset doesn't perform the DML operation itself. If you don't want to see the latter count, turn NOCOUNT to ON. This, of course, means that if fewer rows are effected in your TRIGGER, you won't know about it in the output from SSMS (but it's just informational anyway).
It is also heavily advised that you don't use ROLLBACK inside a TRIGGER, handle transactions outside the TRIGGER, not inside. RAISERROR isn't recommend either and you should be using THROW for new development work (that's been recommended since 2012!). This results in a TRIGGER like below:
CREATE OR ALTER TRIGGER MyTrigger ON dbo.MyTable INSTEAD OF DELETE AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM deleted WHERE SomeVal = 'Nonsense')
THROW 95302, N'Error Msg', 16; --Use an error number appropriate for you
ELSE
DELETE MT FROM dbo.MyTable MT JOIN deleted ON MT.id = deleted.id;
END;
GO

How to run SQL trigger

I am completely new to SQL Server 2008 and I wrote a trigger and would like to be executed only of hassubproduct and spdisplaytype columns are updated or inserted and if they have a value and are not empty.
Any help is appreciated.
CREATE TRIGGER [dbo].[hassubproductcheck]
ON [dbo].[products]
WITH EXECUTE AS CALLER
FOR INSERT, UPDATE
AS
BEGIN
UPDATE products
SET hassubproduct = LTRIM(RTRIM(hassubproduct))
UPDATE products
SET spdisplaytype = LTRIM(RTRIM(spdisplaytype))
END
GO
Something along these lines is probably more like what you want.
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].[products]
WITH EXECUTE AS CALLER FOR INSERT, UPDATE
AS BEGIN
Update p
set hassubproduct = LTRIM(RTRIM(i.hassubproduct))
, spdisplaytype = LTRIM(RTRIM(i.spdisplaytype))
from Products p
join inserted i on i.PrimaryKey = p.PrimaryKey
where i.hassubproduct > ''
OR i.spdisplaytype > ''
END
You have to use the below code to define the trigger. This will help for updated. The same way we need to create one for inserted as well.
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].products]
AFTER UPDATE
AS
INSERT INTO DBO.SAMPLE_TRIGGER
SELECT hassubproduct, 'UPDATE(PREVIOUS)' [TABLE-UPDATE] FROM DELETED
GO
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].products]
AFTER UPDATE
AS
INSERT INTO DBO.SAMPLE_TRIGGER
SELECT hassubproduct, 'UPDATE (LATEST)' [TABLE-UPDATE] FROM DELETED
GO

Trigger - is it necessary BEGIN / COMMIT TRAN

I want to make INSTEAD OF trigger like this:
CREATE TRIGGER [dbo].[DeleteCompany]
ON [dbo].[Company]
INSTEAD OF DELETE
AS
DECLARE #CompanyID int
SELECT #CompanyID = deleted.CompanyID FROM deleted
BEGIN TRAN
DELETE FROM Project WHERE CompanyID = #CompanyID
DELETE FROM CompanyPerson WHERE CompanyID = #CompanyID
UPDATE PersonCompany SET CompanyID = null WHERE CompanyID = #CompanyID
DELETE [Company]
FROM DELETED D
INNER JOIN [Company] T ON T.CompanyID = D.CompanyID
COMMIT TRAN
So, I can be sure, that these actions is one atomic action. But it make sense or TRIGGER always execute inside transaction?
Also, what happens if company will be deleted inside another TRIGGER like this:
CREATE TRIGGER [dbo].[DeleteSecurityLevel]
ON [dbo].[SecurityLevel]
INSTEAD OF DELETE
AS
DECLARE #SecurityLevelID int
SELECT #SecurityLevelID = deleted.SecurityLevelID FROM deleted
BEGIN TRAN
DELETE FROM Company WHERE SecurityLevelId = #SecurityLevelID
DELETE FROM CompanyRole WHERE SecurityLevelId = #SecurityLevelID
....
DELETE SecurityLevel
FROM DELETED D
INNER JOIN SecurityLevel T ON T.SecurityLevelID = D.SecurityLevelID
COMMIT TRAN
so, trigger DeleteSecurityLevel is deleting Company and call DeleteCompany trigger. It would be in one transaction if each trigger has BEGIN/COMMIT TRAM ? if each trigger does not have it?
PS. I can't set "CASCADE DELETE" because DB has some relationships like it:
so, try to set CASCADE DELETE will throw error like it:
Introducing FOREIGN KEY constraint 'FK_Persons_Areas' on table
'Persons' may cause cycles or multiple cascade paths. Specify ON
DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints. Could not create constraint or index. See previous
errors.
All DML statements are executed within a transaction. The DML within the trigger will use the transaction context of the statement that fired the trigger so all modifications, inside the trigger and out, are a single atomic operation.
You don't need explicit transactions inside the trigger, they share the same transaction workspace and the batch inside the trigger and the operation invoking it either commit together or rollback together
A trigger always executes in the context of a transaction - every DML statement operates within a transaction. This is normally hidden from view by the fact that Implicit Transactions are set to commit automatically in SQL Server.
If you issue a rollback from within a trigger, this will (as always with rollback) rollback all transactions, whether nested or not.
In general, you wouldn't commit within a trigger, unless (as in your commented out example) you're opening a nested transaction explicitly.
If there are other aspects to your question, I'm unable to work out what they are from your posted example. Although I'm always a fan of people posting actual SQL when asking SQL questions, sometimes a little commentary, or a bullet-point list of actual questions can help.

preventing a specific record from being deleted

I want to prevent a specific record from being deleted. This trigger works fine for that specific record. However, other records still remain when they're being deleted. Why?
ALTER TRIGGER [Globalization].[CountriesTracker]
ON [Globalization].[Countries]
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
IF ((Select COUNT(*) from [Deleted]
Where [Deleted].[CountryId] = '36bd1536-fb56-4ec4-957e-1b3afde16c56') = 1)
BEGIN
RAISERROR('You can not delete this specific record!', 0, 0)
ROLLBACK TRANSACTION
RETURN
END
END
How can I ensure that rows not matching the above condition are being deleted as expected?
You have an INSTEAD OF trigger so you need an actual DELETE in it.
I'd also consider simply filtering the protected row out because:
Do you need an error throwing? Or silently ignore?
What about multi row deletes that contain the protected row: abort the whole, or delete the rest?
Something like:
ALTER TRIGGER [Globalization].[CountriesTracker] ON [Globalization].[Countries]
INSTEAD OF DELETE
AS
SET NOCOUNT ON;
DELETE
CT
FROM
[Globalization].[Countries] C
JOIN
DELETED D ON C.CountryId = D.CountryId
WHERE
[Deleted].[CountryId] <> '36bd1536-fb56-4ec4-957e-1b3afde16c56'
GO
Because this is INSTEAD OF you still need to perform the delete operation for the default case.

Resources