SQL trigger for update - sql

I was just trying to figure out how to do a basic trigger when I updated a row
Heres the setup
CREATE TABLE marriage(
personid int
married varchar(20)
);
INSERT INTO marriage
values (1, unmarried);
What im trying to do is create a sql trigger that will make it so that when I update a person can only go from married to divorced but not unmarried to divorced.
If anyone can help me with structuring this that would be great

This is what I was looking for if someone was looking for something similar
alter trigger
trigtest3
on married
for update
as
begin
declare #old varchar(20)
declare #new varchar(20)
select #old = married from deleted
select #new = married from inserted
if(#old like 'Unmarried' AND #new like 'Divorced')
rollback
end

SQL Server doesn't provide per-row triggers unfortunately, but only triggers for a complete command. And one single update command can update several rows, so you must look whether at least one affected row has undergone a forbidden change. You do this by joining the deleted and inserted pseudo tables on a column or a combination of columns that uniquely identify a record (i.e. the primary key).
create trigger trg_upd_married on marriage for update as
begin
declare #error_count int
select #error_count = count(*)
from deleted d
join inserted i on i.id = d.id
where d.married = 'Unmarried'
where i.married = 'Divorced'
if #error_count > 0
begin
raiserror('Unmarried persons cannot get divorced.', 16, 121)
rollback transaction
end
end;
The above trigger may still have errors. I am not fluent with TSQL (and just notice that I find its triggers quite clumsy - at least compared to Oracle's triggers I am used to).

You need to use instead of triggers as you need to prevent update. For update triggers are run after the insert happens. Use the following code -
create trigger abc on marriage
for instead of update
as
begin
Begin transaction
if exists(select 1 from deleted as a
inner join inserted as b
on a.personid = b.personid
where a.married = 'unmarried' and b.married = 'Divorced')
begin
raiserror('Status can not be changed from unmarried to Divorced',16,1)
Rollback transaction
end
else
begin
update a
set a.married = b.married
from marriage as a
inner join inserted as b
on a.personid = b.personid
Commit transaction
end
end
Let me know if this helps

Related

Delete trigger and getting field from another table

I have this delete trigger on an SQL database. The record deletes currently and gets written to an audit table. I have been asked to include in this history table a field from another table that is related to the record being deleted based on SurveyID. I thought I could do something like
select #Status = Status from table where Survey = deleted.Survey
But this is incorrect syntax.
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
Declare #SurveyId int
Declare #StudentUIC varchar(10)
Declare #Status varchar(10)
select #SurveyId = deleted.SurveyID,
#StudentUIC = deleted.StudentUIC
from deleted
select #Status = Status from tbly when SurveyID = deleted.SurveyID
insert into fupSurveyAudit
values(#SurveyId,#StudentUIC,#Status)
End
Arrgh. I think you want this insert in your trigger (and nothing else):
insert into fupSurveyAudit(SurveyId, StudentUIC, status)
select d.SurveyId, d.StudentUIC, y.status
from deleted d left join
tbly y
on d.SurveyId = y.SurveyId;
Notes:
deleted could contain more than one row, so assuming that it has one row can lead to a run-time error or incorrect results.
A left join is needed in case there is no matching row for the status.
You should always include the columns in an insert
Your archive table should have additional columns, such as an identity column and the date of the insert, which are set automatically (and hence not explicitly part of the insert).
Triggers are fired once for each statement (Delete,insert,update) not for each row inside the statement.
You cannot use variables here because when multiple lines are deleted from the table only one line will be inserted in the Audit table because the variable can only hold one value.
You just need a simple insert from the deleted table into the Audit table something like this....
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit(SurveyId, StudentUIC,[Status])
select d.SurveyID
,d.StudentUIC
,y.[Status]
from deleted d
INNER JOIN tbly y ON y.SurveyID = deleted.SurveyID
End
Try this
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit -- Better listed the column list here
select
d.SurveyID, d.StudentUIC, y.Status
from
deleted d JOIN tbly y ON d.SurveyID = y.SurveyID
End

Using SQL Server triggers to impose constraints

I understand the idea of triggers but a bit confused on how to use it to impose constraints. For example I have two tables: a student table and a book_order table that shows what book a student orders.
I want to create a trigger that will check that a student with a book order cannot be deleted from the student table.
Not sure why you would ever do this unless you wanted to write to a log or something when it happened but.....
create TRIGGER Del_Student
ON dbo.Student
INSTEAD OF DELETE
AS
BEGIN
Declare #cnt int
Select #cnt = count(*) from deleted d
Inner Join BookOrders o on d.studentId = o.studentId
if (#cnt > 0)
BEGIN
RAISERROR ('Deletions not allowed from this table when bookorders exist for student)', 16, 1)
END
END

SQL Server : create trigger to replace old value to new value on another table

I am using SQL Server 2008. I want to create a trigger for update which will fire on update of user table.
Trigger functionality: replace user_tbl updated mobile number to user_work_tbl.
CREATE TRIGGER [dbo].[tr_User_Modified]
ON [dbo].[user_tbl]
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
DECLARE #MobileNo varchar(11)
IF UPDATE (mobile_no)
BEGIN
DECLARE #MobileNo VARCHAR(50)
SELECT #MobileNo = mobile_no
FROM [dbo].user_tbl
UPDATE [dbo].[user_work_tbl]
SET mobile_no = #MobileNo
WHERE [dbo].[user_work_tbl].mobile_no = #oldMobileNo // here I have a problem
END
END;
In the comment "here I have a problem" I need a mobile number which exists in user_tbl before update so that the only row of user_work_tbl gets updated.
Any suggestions to do this are also accepted.
Thanks for your all response
You need to join three tables together in your trigger - user_work_tbl, inserted and deleted. However, its not clear at the moment exactly what conditions are required:
CREATE TRIGGER [dbo].[tr_User_Modified]
ON [dbo].[user_tbl]
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
IF UPDATE (mobile_no)
BEGIN
UPDATE u
SET mobile_no=i.mobile_no
FROM user_work_tbl u
inner join
deleted d
on u.mobile_no = d.mobile_no
inner join
inserted i
on
i.PKCol = d.PKCol --What's the PK of user_tbl?
END
END;
inserted and deleted are pseudo-tables that contain the new and old rows that were affected by a particular statement, and have the same schema as the original table. They're only accessible from within the trigger.
Note, also, that the above trigger is correct, even when multiple rows are updated in user_tbl - provided you can correctly relate inserted and deleted in the final ON clause.
You can get the old phone number from the table deleted and the new one from inserted, but you should use user primary key the update the rows.

INSERT Trigger not firing an update?

Ok so i have a fairly basic trigger :
In words, After Insert, Get IP information and update the Inserted row with the new data
CREATE TRIGGER [BasicData.IPInfo.Gather]
ON [BasicData]
AFTER INSERT
AS
BEGIN
DECLARE #City VARCHAR(1000),
#Country VARCHAR(1000),
#IP VARCHAR(1000),
#ROWID UNIQUEIDENTIFIER
SELECT #IP=[IP],#ROWID=[ID] FROM [inserted]
SELECT #Country = [Country], #City= [City]
FROM [IPInfo] WHERE [IP] = #IP
IF (#City IS NOT NULL) AND (#Country IS NOT NULL)
BEGIN -- Never seems to fire
UPDATE [BasicData]
SET [IPCountry]=#Country,[IPCity]=#City
WHERE [ID] = #ROWID
END
ELSE
BEGIN -- Fired correctly
INSERT INTO [IPInfo.Missing] VALUES (#IP)
END
END
Now the problem is, It adds the missing IP information correctly (Only when missing), however, it does not seem to ever update the table when it does, What am i missing?
Ive tweaked it in every possible way i could think of... (My trigger knowledge is rather bad)
There are a few problems:
INSERTED can contain many rows. Your trigger allows for only one.
The trigger is AFTER INSERT, and I guess this might exclude UPDATEs. Try AFTER INSERT, UPDATE
Your IF statement isn't checking for an UPDATE - what if the column is UPDATED to NULL? it won't catch it even though it was an update. What if something inserted NON NULL data? it will think it was an UPDATE.
As far as I know the only way to identify an UPDATE is to join INSERTED and `DELETED on the PK. If there is a match, it's been updated.
Perhaps you could rewrite it like this:
CREATE TRIGGER [BasicData.IPInfo.Gather]
ON [BasicData]
AFTER INSERT, UPDATE
AS
BEGIN
-- Save UPDATES to BasicData
UPDATE [BasicData]
SET [IPCountry]=I.Country,[IPCity]=I.City
FROM [BasicData] UT
INNER JOIN
[inserted] I
ON I.ID = UT.ID
INNER JOIN
Deleted D
ON D.ID = I.ID
INNER JOIN
[IPInfo] IP
ON I.ID = IP.ID
-- Save inserts to Missing
INSERT INTO [IPInfo.Missing] (IP)
SELECT IP FROM
INSERTED I
WHERE NOT EXISTS (SELECT 1 FROM DELETED D WHERE D.ID = I.ID)
END

T-SQL: How to deny update on one column of a table via trigger?

Question:
In our SQL-Server 2005 database, we have a table T_Groups.
T_Groups has, amongst other things, the fields ID (PK) and Name.
Now some idiot in our company used the name as key in a mapping table...
Which means now one may not alter a group name, because if one does, the mapping is gone...
Now, until this is resolved, I need to add a restriction to T_Groups, so one can't update the group's name.
Note that insert should still be possible, and an update that doesn't change the groupname should also be possible.
Also note that the user of the application & the developers have both dbo and sysadmin rights, so REVOKE/DENY won't work.
How can I do this with a trigger ?
CREATE TRIGGER dbo.yournametrigger ON T_Groups
FOR UPDATE
AS
BEGIN
IF UPDATE(name)
BEGIN
ROLLBACK
RAISERROR('Changes column name not allowed', 16, 1);
END
ELSE
BEGIN
--possible update that doesn't change the groupname
END
END
CREATE TRIGGER tg_name_me
ON tbl_name
INSTEAD OF UPDATE
AS
IF EXISTS (
SELECT *
FROM INSERTED I
JOIN DELETED D ON D.PK = I.PK AND ISNULL(D.name,I.name+'.') <> ISNULL(I.name,D.name+'.')
)
RAISERROR('Changes to the name in table tbl_name are NOT allowed', 16,1);
GO
Depending on your application framework for accessing the database, a cheaper way to check for changes is Alexander's answer. Some frameworks will generate SQL update statements that include all columns even if they have not changed, such as
UPDATE TBL
SET name = 'abc', -- unchanged
col2 = null, -- changed
... etc all columns
The UPDATE() function merely checks whether the column is present in the statement, not whether its value has changed. This particular statement will raise an error using UPDATE() but won't if tested using the more elaborate trigger as shown above.
This is an example of preserving some original values with an update trigger.
It works by setting the values for orig_author and orig_date to the values from the deleted pseudotable each time. It still performs the work and uses cycles.
CREATE TRIGGER [dbo].[tru_my_table] ON [dbo].[be_my_table]
AFTER UPDATE
AS
UPDATE [dbo].[be_my_table]
SET
orig_author = deleted.orig_author
orig_date = deleted.orig_date,
last_mod_by = SUSER_SNAME(),
last_mod_dt = getdate()
from deleted
WHERE deleted.my_table_id IN (SELECT DISTINCT my_table_id FROM Inserted)
ALTER TABLE [dbo].[be_my_table] ENABLE TRIGGER [tru_my_table]
GO
This example will lock any updates on SABENTIS_LOCATION.fk_sabentis_location through a trigger, and will output a detailed message indicating what objects are affected
ALTER TRIGGER dbo.SABENTIS_LOCATION_update_fk_sabentis_location ON SABENTIS_LOCATION
FOR UPDATE
AS
DECLARE #affected nvarchar(max)
SELECT #affected=STRING_AGG(convert(nvarchar(50), a.id), ', ')
FROM inserted a
JOIN deleted b ON a.id = b.id
WHERE a.fk_sabentis_location != b.fk_sabentis_location
IF #affected != ''
BEGIN
ROLLBACK TRAN
DECLARE #message nvarchar(max) = CONCAT('Update values on column fk_sabentis_location locked by custom trigger. Could not update entities: ', #affected);
RAISERROR(#message, 16, 1)
END
Some examples seem to be using:
IF UPDATE(name)
But this seems to evaluate to TRUE if the field is part of the update statement, even if the value itself has NOT CHANGED leading to false positives.