preventing a specific record from being deleted - sql-server

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.

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

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

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;

MS SQL trigger instead of update ELSE possible to execute original query?

Here is my trigger
Create TRIGGER [dbo].[tri_before_update]
ON [dbo].[test]
instead of update
AS
BEGIN
SET NOCOUNT ON;
if update (test_a)
begin
*.. my update & insert query*
end
END
create TRIGGER [dbo].[tri_before_update_price]
ON [dbo].[co_ticket]
instead of update
AS
BEGIN
SET NOCOUNT ON;
if update (t_price)
begin
insert into old_price_log (t_id,insert_time,process_id,old_t_price)
select i.t_id,getdate(),2,t_price
from Inserted i,co_ticket t where i.t_id = t.t_id
update t set t_price = i.t_price
from co_ticket t, inserted i
where t.t_id = i.t_id
end
else
begin
-- if update other then (t_price) then the update comand not execute.
-- example when i update t_cancel_flag or t_quantity and etc. end
END
This trigger execute perfectly when i update on column "test_a". HOWEVER, when i update other than column "test_a" it won't be execute. I know i can put "else" command, but i got a lot of column. sometimes will update two other column , sometimes three or four column. I don't wish to update all column everytime. Is it possible ELSE "then execute original query"?
I tried a lot different way but still can't work. :( Please HELP!
create TRIGGER [dbo].[tri_on_update_price]
ON [dbo].[co_ticket]
AS
BEGIN
SET NOCOUNT ON;
if update (t_price)
begin
insert into old_price_log (t_id,insert_time,process_id,old_t_price)
select d.t_id, getutcdate(),2,d.price
from deleted d
END
end
An ordinary after trigger will do just what you want: insert a log of the price change, if the price was updated. No need for INSTEAD OF. You need to look into the deleted pseudo-table to get the old price. Never store local times in a database.

Is there any way to store field value only once?

Is there any way to store (while inserting) a value on column which can never be changed ?
Like example :
insert to DB row "a,b,c,x,s,X" - and the X *wont* be able to change.
(however , if the whole row is deleted - its fine"
You can remove UPDATE privilieges for that column from the users you don't want to be able to change the value. Alternatively, use an update trigger to prevent updates.
The update trigger is easier if you want to disallow updates from any user.
You can write a trigger before updating or after updating ( http://msdn.microsoft.com/en-us/library/ms188601.aspx ) the rows to check whether it is going to change or not. Here is an example trigger after update , it will rollback transaction if column modified.
CREATE TRIGGER tr_update on YourTable AFTER UPDATE AS
IF UPDATE(YourColumn)
BEGIN
RAISERROR ('cannot change yourColumn', 16, 1)
ROLLBACK TRAN
RETURN
END
GO
Add an Update Trigger, where you check for a change:
CREATE TRIGGER YourTrigger On YourTable FOR UPDATE
AS
IF EXISTS (SELECT 1
FROM INSERTED i
INNER JOIN DELETED d ON i.PK=d.PK
WHERE i.columnX!=d.columnX
OR (i.columnX IS NULL and d.columnx IS NOT NULL)
OR (d.columnX IS NULL and i.columnx IS NOT NULL)
)
BEGIN
ROLLBACK
RAISERROR('Error, can''t change columnX',16,1)
RETURN
END
GO

Resources