Trigger that updates a column on the row where I clicked update - sql-server

So I have this table:
ID Name IsDeleted
1 test True
2 test2 False
I currently have the following trigger:
CREATE TRIGGER [dbo].[Trigger_test_IsDeleted]
ON [dbo].[test]
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.test
SET IsDeleted = 1
WHERE ID = 1;
END
When I click the delete button on a row in my index view, I need this trigger to launch and set IsDeleted to 1 (or True) on that exact row. However, doing it with the above trigger will obviously delete only the row source where ID = 1. And if I leave out the WHERE statement, it sets IsDeleted for all rows to true.
I need my trigger to dynamically determine where "delete" is being pressed and only delete that row.
I know that in MySQL, you can accomplish this by using the following in a delete trigger:
SET new.IsDeleted = 1
Is there a way to get the same effect as "new." in SQL Server triggers?
And since we're on the topic, I want to achieve the same thing, but with altering the current code in the controller. Here is what it looks like now:
//POST - Delete
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int? id)
{
var test = await _db.Test.FindAsync(id);
if (test== null)
{
return View();
}
_db.Test.Remove(test);
await _db.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
This code will achieve an ordinary delete operation on the database. I want to alter it to set "IsDeleted" to 1 instead of deleting the record.

If you want to update a flag instead of delete (which some people call it SOFT DELETE), you should update your trigger like this:
CREATE TRIGGER [dbo].[Trigger_test_IsDeleted]
ON [dbo].[test]
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.test
SET deleted.IsDeleted = 1
WHERE ID = deleted.ID; --<-- This is the key point
END

You need to examine the 'deleted' virtual table, and perform the update accordingly. EG:
create table test(id int primary key, IsDeleted bit default 0)
go
CREATE OR ALTER TRIGGER [dbo].[Trigger_test_IsDeleted]
ON [dbo].[test]
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.test
SET IsDeleted = 1
WHERE ID in (select id from deleted);
END
go
insert into test(id, IsDeleted) values (1,0),(2,0),(3,0)
delete from test where id = 2
select * from test
outputs
id IsDeleted
----------- ---------
1 0
2 1
3 0
(3 rows affected)

Related

ROLLBACK make data in INSERTED table is removed in AFTER INSERT TRIGGER

I give an example to show my problem. I created a table like this:
CREATE TABLE a
(
id INT
)
I then created an AFTER INSERT trigger to not allow insert id = 1 into table a:
CREATE TRIGGER [dbo].[insert_a]
ON [dbo].[a] AFTER INSERT
AS
BEGIN
DECLARE #id INT
SELECT #id = id FROM inserted
IF #id = 1
BEGIN
RAISERROR('1', 12, 1)
ROLLBACK;
END
SELECT * FROM inserted
END
Then I insert id = 1 into table a:
INSERT INTO a VALUES(1)
I get nothing from INSERTED table.
I realize that when I ROLLBACK then + the data in table a was rolled back (I know) and data in INSERTED table is also removed. Why is that?
If I change the AFTER INSERT trigger to an INSTEAD OF INSERT trigger:
ALTER TRIGGER [dbo].[insert_a]
ON [dbo].[a] INSTEAD OF INSERT
AS
BEGIN
DECLARE #id INT
SELECT #id = id FROM inserted
IF #id = 1
BEGIN
RAISERROR('1', 12, 1)
ROLLBACK
END
SELECT * FROM inserted
END
INSERT INTO a VALUES(1)
Then I get the result:
id
1
That means data in INSERTED table is not removed though have been ROLLBACK.
Help me explain deeply what happens inside trigger?
This is the intended behaviour as far as I know. It's just that AFTER may be a bit misleading depending on how you look at it.
"The trigger and the statement that fires it are treated as a single transaction, which can be rolled back from within the trigger. If a severe error is detected, the entire transaction automatically rolls back.".
https://msdn.microsoft.com/en-us/library/ms178110.aspx

Trigger for both insert and update

I'm trying to create a trigger that will prevent a user from inserting to or updating the quantity in my orderLines table if the amount is greater than the quantity on the products table.
Is there a way to do it in a single trigger or do I have to create to separate ones for both insert and update actions?
Below is how my trigger starts:
CREATE TRIGGER OrdersLines_ITrig
ON ordersLines
FOR INSERT
AS
Depends on the BEGIN/END blocks:
Triggers have special INSERTED and DELETED tables to track "before" and "after" data. So you can use something like IF EXISTS (SELECT * FROM DELETED) to detect an update. You only have rows in DELETED on update, but there are always rows in INSERTED.
CREATE TRIGGER dbo.TriggerName
ON dbo.TableName
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
BEGIN
----Do update
END;
IF EXISTS (SELECT * FROM Inserted) AND NOT EXISTS (SELECT * FROM deleted)
BEGIN
-----Do insert
END;
END
This is a simple requirement you can handle using CHECK CONSTRAINT itself. If you are defining trigger, you have to properly rollback the transaction. Have proper error message etc. You can simply have a check constraint, which will do all these things for you.
I would suggest you to do below steps:
Create a user defined function, which returns TRUE or FALSE, based on the quantity in the Product table.
CREATE FUNCTION CheckQuantity(#productID INT)
RETURNS BIT
AS
BEGIN
---LOGIC
END
Leverage the user defined function in the CHECK constraint.
ALTER TABLE OrderLines ADD CONSTRAINT CHK_Quantity CHECK( dbo.CheckQuantity(ProductId) = = 1)

How to deal with update of PK in ON INSTEAD OF UPDATE Trigger

I am trying to construct an INSTEAD OF UPDATE trigger that modifies one of the columns being inserted, before doing the insert. Works fine if the PK of the table is not changed during the update.
If the PK of the table is updated, I don't know how to write an update statement to update the appropriate rows. How do I know which rows to update.
CREATE Table MyTest(ID int NOT NULL PRIMARY KEY,
Name NVARCHAR(40) NOT NULL);
GO
INSERT INTO MyTest VALUES(1,'Joe');
INSERT INTO MyTest VALUES(2,'Scott');
INSERT INTO MyTest VALUES(3,'Dan');
INSERT INTO MyTest VALUES(4,'Becky');
GO
CREATE TRIGGER Update_Please
ON MyTest
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON
IF EXISTS (SELECT id
FROM INSERTED
EXCEPT
SELECT id
FROM DELETED)
RAISERROR ('now what?',-1,-1);
/* ======================================================
oh heck! The PK in one or more rows was updated
in the update statement. How do I construct
an update statement w/o joining to inserted on the PK?
====================================================== */
ELSE
BEGIN
-- ALTER A COLUMN
UPDATE M
SET name = CASE WHEN I.NAME = 'Mike' THEN 'The Dude' ELSE I.NAME END
FROM MyTest M
JOIN INSERTED I ON I.id = M.id;
END
END
GO
UPDATE MyTest SET Name = 'Mike' WHERE id > 2; --works great!
UPDATE MyTest SET ID = 9 WHERE Name = 'Joe'; --how do I complete the update?
As I wrote in my comment, I don't think you can do that in an instead of update trigger, however it's quite easy to do that with a for update. The reason is the difference between the two triggers - the instead of update trigger is fired before the underlying table is updated, while the for update trigger is fired after that. This means that whatever values you have in your inserted table are the same values you have in your actual table - so just change the trigger from instead of update to a for update:
CREATE TRIGGER Update_Please
ON MyTest
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE M
SET name = CASE WHEN I.NAME = 'Mike' THEN 'The Dude' ELSE I.NAME END
FROM MyTest M
JOIN INSERTED I ON I.id = M.id;
END
GO

Creating a trigger on SQL Server 2008 R2 to catch a record update

Using Great Plains here and one of our users keeps screwing up customer data so we want to put a trigger on the customer table and so we can find out who it is.
Anyway, I created a table called audit_RM00101 as follows:
DATE nchar(10)
CUSTNMBR char(15)
CUSTNAME char(65)
UPSZONE char(3)
SALSTERR char(15)
USERID nchar(100)
I want to capture those same fields from the table I want to audit so I wrote the trigger as follows:
CREATE TRIGGER CatchCustomerRegionUpdate
ON RM00101
FOR UPDATE
AS
DECLARE #UserID VARCHAR(128)
SELECT #UserID = system_user
INSERT INTO audit_RM00101
SELECT DATE, CUSTNMBR, CUSTNAME, UPSZONE, SALSTERR, #UserID FROM UPDATED
The trigger gets created just fine but when I try to test it by updating a customer record in Great Plains, Great Plains throws up an ugly error and the trigger doesn't get fired.
What am I doing wrong here?
Thanks.
in a trigger, you get the DELETED and INSERTED tables, there is no UPDATED, so replace FROM UPDATED with FROM INSERTED
also try to fix your USERID column, your audit_RM00101.USERID is a nchar(100) while #UserID is a VARCHAR(128).
EDIT based on OPs comment: Ah, so there is no way to audit when a table is updated by using a trigger?
in a trigger when deleting, DELETED is populated, but INSERTED is empty
in a trigger when updating, DELETED is populated with the original value, and INSERTED is populated with the newly updated values
in a trigger when inserting, DELETED is empty, but INSERTED has the newly inserted values
There is no UPDATED in SQL Server; just inserted and deleted.
Also, it makes sense to add IF ##ROWCOUNT = 0 RETURN in the very beginning of triger's body.
When UPDATE takes place, both inserted and deleted tables are not empty. You may add the following code to make sure you handle UPDATE, not insert/delete:
IF EXISTS(SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
BEGIN
-- handle update
END ;
It's not really important for your trigger because you specify just FOR UPDATE, it would be important if you had, for instance, FOR UPDATE, INSERT, DELETE.
we have only two magic tables called INSERTED and DELETED
update indirectly is a Delete statement followed by Insert statement. so you have to update the column's value which is present in INSERTED.
CREATE TRIGGER CatchCustomerRegionUpdate
ON RM00101
AFTER UPDATE
AS
BEGIN
DECLARE #INSERTED INT, #DELETED INT
SET #INSERTED = SELECT COUNT(*) FROM INSERTED
SET #DELETED = SELECT COUNT(*) FROM DELETED
IF #INSERTED = 1 AND #DELETED = 1
BEGIN
UPDATE TABLE1
SET COL1 = INSERTED_COL1
WHERE IDCOL = INSERTED_IDCOL
END
END

SQL Server Update Trigger

I have never used triggers before in SQL server and I have looked around online but haven't found an answer to my question. Basically I am trying to write an Trigger that will run after a record has been updated in a table. This trigger will then update two additional tables based on the record that was updated in the first table.
The primary table with the trigger on it will be updating one record using a query like this:
UPDATE E.SM_T_RList
SET IsActive = 0
WHERE Guid = #Guid
I then want the trigger to do something like this:
ALTER TRIGGER [E].[IsActiveUpdate]
ON [E].[SM_T_RList]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE E.SM_T_BInfo
SET IsActive = 0
WHERE Guid = #Guid
UPDATE E.SM_T_RMachines
SET IsActive = 0
WHERE GUID = #GUID
END
The Guid that I want updated is being used by the primary table. But I can't figure out how I get the #Guid that I want updated into the trigger? Please help.
Thanks
Both the answers already posted suffer from the same problem - they're marking the other rows as Inactive, whenever any update occurs on your base table
Something like:
ALTER TRIGGER [E].[IsActiveUpdate]
ON [E].[SM_T_RList]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE E.SM_T_BInfo
SET IsActive = 0
WHERE Guid IN (SELECT Guid FROM INSERTED where IsActive=0)
UPDATE E.SM_T_RMachines
SET IsActive = 0
WHERE Guid IN (SELECT Guid FROM INSERTED where IsActive=0)
END
Would be more appropriate
Triggers in SQL Server operate on sets of rows not individual rows. You access these via the inserted and deleted pseudo tables. Assuming that you might want the value of isactive to cascade when previously inactive rows were made active you could use something like this.
ALTER TRIGGER [E].[IsActiveUpdate]
ON [E].[SM_T_RList]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE E.SM_T_BInfo
SET IsActive = i.IsActive
FROM INSERTED i JOIN E.SM_T_BInfo e
ON e.Guid = i.Guid
UPDATE E.SM_T_RMachines
SET IsActive = i.IsActive
FROM INSERTED i JOIN E.SM_T_BInfo e
ON e.Guid = i.Guid
END

Resources