SQL After Update Trigger confusion - sql

I hope someone can help me. This is my first post ever, so I hope I explained it well.
I am using Microsoft SQL Management Studio.
I am trying to make a trigger that is not letting anyone change the personal number of a person if that person didn't return a computer in a database that I made.
So I am making a trigger for table Person where I have ID_Person primary key, Name, Surname and a Personal_numb.
Also, I made a table called Renting where I have columns: ID_Renting primary key, ID_Person foreign key, ID_Computer foreign key and Date_Rented and Date_Returned columns..
Here is how my code look a like:
create trigger NO
On Person
after update
as
begin
if update(Personal_numb)
(select ID_Person from Renting
where Renting.Date_Returned is null)
begin
Raiserror('Person needs to return the computer before you try to change their personal number!', 16,1)
end
end
However, I know that I am missing something between if update(Personal_numb) and select but what is it?

You could try something like this
drop trigger if exists trg_Person_upd;
go
create trigger trg_Person_upd on Person
after update
as
set nocount on;
if exists(select * from inserted);
begin
if exists (select *
from Renting r
join inserted i on r.ID_Person=i.ID_Person
join deleted d on r.ID_Person=d.ID_Person
where i.Personal_numb<>d.Personal_numb
and r.Date_Returned is null))
throw 50000, 'Person needs to return the computer before you try to change their personal number!', 1;
end

Something like this.
You use inserted to join to renting and checks for Date_Rreturned using EXISTS()
Note : I changed the trigger name. Naming it NO is rather unusual
create trigger TR_Person
On Person
after update
as
begin
if update(Personal_numb)
and exists
(
select *
from inserted i
inner join Renting r on i.ID = r.ID_Person
where r.Date_Returned is null
)
begin
Raiserror('Person needs to return the computer before you try to change their personal number!', 16,1)
end
end
go

Related

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 trigger for update

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

Performing operation on Select statement result

I am creating a stored procedure which takes following parameters:
#LessonId char(15), #TeacherName char(25), #TeacherSessionId char(10),
#CurrentTeacher char(25) OUTPUT
This is what I want to do:
Inserts a new row if the LessonId doesn't exist in the table
If it already exists it needs to do the following:
a) check if the TeacherName & TeacherSessionId are the same as mine ( input parameters), if yes just update the LoginTime
b) if the TeacherName OR TeacherSessionId in database are not the same as mine, this lesson belongs to someone else so return the teacher's name
What is the best way to avoid running too many SELECT operations on the table.
I know that I can do something like this:
IF NOT EXISTS (SELECT * FROM Lessontb WHERE LessonId = #LessonId )
BEGIN
INSERT...
END
ELSE
BEGIN
IF EXISTS (SELECT TeacherName FROM Lessontb
WHERE TeacherName <> #TeacherName
OR TeacherSessionId <> #TeacherSessionId )
BEGIN
SET #CurrentTeacher = TeacherName
END
IF EXISTS (SELECT TeacherName FROM Lessontb
WHERE TeacherName = #TeacherName
AND TeacherSessionId = #TeacherSessionId )
BEGIN
UPDATE ... LoginTime
END
END
I am quite sure there must be a more clean way to do this, for instance running the 2nd and 3rd steps on the result set of the first SELECT but I am not sure how.
Potential problem. It appears that you are using LessionID as the key column. But later down the code, you ignore the key column.
Potential solution: Do one select and load them into variables, also save the clustered key column into the variable as well. Work out your logic and then issue one update. If you update with the clustered key, the overhead would be very less.
Naz

SQL Server : delete row if no constraint error

Much more complicated then this, but this is the basic
Person table (id, name, emailaddress)
Salesperson table (id, personID)
CustomerServiceRep table (id, personID)
Jeff is salesperson (id=4) and customerservicerep (id=5) with personID=1.
Simple
Trigger on SalesPerson Table
AFTER DELETE
AS
DECLARE #personID int = (SELECT personID FROM deleted);
IF #personID IS NOT NULL
BEGIN TRY
DELETE FROM Person
WHERE Person.id = #personID;
END TRY
BEGIN CATCH
END CATCH
DELETE FROM SalesPerson WHERE id=4;
Causes
Msg 3616, Level 16, State 1
An error was raised during trigger execution. The batch has been aborted and the user transaction, if any, has been rolled back.
I'm sure there's a much simpler way to not delete personID if it exists from some kind of constraint. Or catch the constraint. To go through every possible table that this could be in seems very repetitive and potentially more difficult when there are more tables/columns that may use this same table/constraint (foreign key).
You need an instead of delete trigger here rather than an after trigger.
CREATE Trigger tr_Delete_person
on Person
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
-- Delete any child records
Delete FROM SalesPerson
WHERE EXISTS (SELECT 1 FROM deleted
WHERE personID = SalesPerson.personID)
Delete FROM CustomerServiceRep
WHERE EXISTS (SELECT 1 FROM deleted
WHERE personID = CustomerServiceRep .personID)
-- Finally delete from the person table
DELETE FROM Person
WHERE EXISTS (SELECT 1 FROM deleted
WHERE personID = Person .personID)
END
You also have a fundamental flaw in your trigger in that you seem to expect that the trigger will be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Deleted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
DECLARE #personID int = (SELECT personID FROM deleted);
It's undefined - you'll get the value from one, arbitrary row in Deleted, and all others are ignored - typically not what you want!
You need to rewrite your entire trigger with the knowledge the Deleted WILL contain multiple rows! You need to work with set-based operations - don't expect just a single row in Deleted !

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.