Using SQL Server triggers to impose constraints - sql

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

Related

Insert trigger doesnt do what i want it to do

i made a trigger which should avoid inserting a record in the rental 'uitlening' table if the person has an overdue payment (Boete). Unfortunately it doesnt work and i cant find the reason why. 'Boete' is an attribute of another table than rental. Can someone help me?
CREATE TRIGGER [dbo].[Trigger_uitlening]
ON [dbo].[Uitlening]
FOR INSERT
AS
BEGIN
DECLARE #Boete decimal(10, 2);
SET #Boete = (SELECT Boete FROM Lid WHERE LidNr = (SELECT LidNr FROM inserted));
IF #Boete = 0
BEGIN
INSERT INTO Uitlening
SELECT *
FROM inserted;
END;
END;
It sounds like what you actually need is a cross-table constraint.
You can either do this by throwing an error in the trigger:
CREATE TRIGGER [dbo].[Trigger_uitlening]
ON [Rental]
AFTER INSERT
AS
SET NOCOUNT ON;
IF EXISTS (SELECT 1
FROM inserted i
INNER JOIN dbo.Person p ON i.[personID] = p.[personID]
WHERE p.[PaymentDue] <= 0
)
THROW 50001, 'PaymentDue is less than 0', 1;
A better solution is to utilize a trick with an indexed view. This is based on an article by spaghettidba.
We first create a dummy table of two rows
CREATE TABLE dbo.DummyTwoRows (dummy bit not null);
INSERT DummyTwoRows (dummy) VALUES(0),(1);
Then we create the following view:
CREATE VIEW dbo.vwPaymentLessThanZero
WITH SCHEMBINDING -- needs schema-binding
AS
SELECT 1 AS DummyOne
FROM dbo.Rental r
JOIN dbo.Person p ON p.personID = r.personID
CROSS JOIN dbo.DummyTwoRows dtr
WHERE p.PaymentDue <= 0;
This view should in theory always have no rows in it. To enforce that, we create an index on it:
CREATE UNIQUE CLUSTERED INDEX CX_vwPaymentLessThanZero
ON dbo.vwPaymentLessThanZero (DummyOne);
Now if you try to add a row that qualifies for the view, it will fail with a unique violation, because the cross-join is doubling up the rows.
Note that in practice the view index takes up no space because there are never any rows in it.
Assuming you just want to insert records into [Rental] of those users, who have [PaymentDue] <= 0. As you mentioned in your last comment:
no record in rental can be inserted if the person has a PaymentDue
thats greater than zero
And other records should be silently discarded as you didn't give a clear answer to #Larnu's question:
should that row be silently discarded, or should an error be thrown?
If above assumptions are true, your trigger would look like:
CREATE TRIGGER [dbo].[Trigger_uitlening]
ON [Rental]
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO [Rental] ( [DATE], [personID], [productID])
SELECT i.[DATE], i.[personID], i.[productID]
FROM INSERTED i
INNER JOIN Person p ON i.[personID] = p.[personID]
WHERE p.[PaymentDue] <= 0
END;
Attention! When you create a trigger by FOR INSERT or AFTER INSERT then don't write insert into table select * from inserted, because DB will insert data automatically, you can do only ROLLBACK this process. But, when creating a trigger by INSTEAD OF INSERT then you must write insert into table select * from inserted, else inserting not be doing.

SQL After Update Trigger confusion

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

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

Insert data into table when i am using trigger?

Here is a trigger
CREATE TRIGGER [dbo].[CheckApplyId]
ON [dbo].[AppliedStudent_event] INSTEAD OF INSERT
AS
DECLARE #studentId INT
DECLARE #compReq_Id INT
BEGIN
SELECT #studentId = studentId
FROM INSERTED
SELECT #compReq_Id = compReq_Id
FROM INSERTED
IF EXISTS(SELECT StudentId,
compreq_id
FROM AppliedStudent_event
WHERE StudentId = #studentId
AND compreq_id = #compReq_Id)
BEGIN
ROLLBACK
PRINT 'User Already Applied'
END
END
When in insert a data into a table using command:
INSERT INTO AppliedStudent_event (StudentId, compreq_id)
VALUES (3026, 1)
Message is:
(1 row(s) affected)
But when I execute a sql command no data is inserted in the table.
Can you please tell why are you using trigger because you use only assign the variable #studentId and #compReq_Id from inserted table.
That's a broken trigger because inserted can contain multiple (or no) rows - so a statement like SELECT #ScalarVariable = column from inserted is always wrong.
And it's unnecessary since you can just place a UNIQUE constraint on the StudentId and compreq_id columns:
ALTER TABLE AppliedStudent_event
ADD CONSTRAINT UQ_Student_Events
UNIQUE (StudentId,compreq_id)
And it's further broken because you've specified it as an instead of trigger - that says that your code is going to be responsible for the actual insert - but your code doesn't actually do that. That's why no data ends up in the table.
If you insist on doing it as a trigger, it's actually tricky to get everything correct (that's why I'd really recommend the UNIQUE constraint). It'll end up being something like this:
CREATE TRIGGER [dbo].[CheckApplyId]
ON [dbo].[AppliedStudent_event] INSTEAD OF INSERT
AS
IF EXISTS(select
StudentId,compreq_id,COUNT(*)
from inserted
group by StudentId,compreq_id
HAVING COUNT(*) > 1)
OR EXISTS (select *
from inserted i
inner join
AppliedStudent_event e
on
i.StudentId = e.StudentId and
i.compreq_id = e.compreq_id)
BEGIN
ROLLBACK
PRINT 'User Already Applied'
END
ELSE
BEGIN
INSERT INTO AppliedStudent_event(StudentId,compreq_id /* Other columns? */)
SELECT StudentId,compreq_id /* And again, other columns */
FROM inserted
END

Counting referenced records by trigger

I've discovered a new problem: I have a two tables, Classes and Students. Students references to Classes by [ClassID] column. Classes have column named [Count], which storing count of referencing students and I'm trying update it via AFTER INSERT,DELETE trigger on Students table.
I wrote a simple CALC_COUNT procedure like that:
CREATE PROCEDURE [dbo].[CALC_COUNT]
#classid INT
AS
BEGIN
UPDATE classes SET [Count] = (SELECT COUNT(Id) FROM students WHERE [ClassID] = #classid);
END
RETURN 0
and use it inside trigger
CREATE TRIGGER [MONITOR_STUDENTS_SCHEMA_TRIGGER]
ON [dbo].[students]
AFTER DELETE, INSERT
AS
BEGIN
UPDATE [dbo].[classes]
SET studentsschemarev +=1 FROM inserted;
CALC_COUNT(SELECT [ClassID] FROM inserted UNION SELECT [ClassID] FROM deleted);
UPDATE [dbo].[stats] SET students_schema_rev += 1;
END
But it not works.
I think, I need a way to execute procedure for each row in SELECT statement of trigger, but I don't know how.
SQL Server 2012 LocalDB, compatibility mode with SQL Server 2008.
You don't need your stored proc.
Update your trigger to be
update classes
set
count = StudentCount,
schemarevcount += 1
from
classes
inner join
(select * from inserted union select * from deleted) students
on classes.classid=students.classid
inner join
(select classid, count(*) as StudentCount from students group by classid) counts
on students.classid = counts.classid
in place of the update and the call to calc_count
An english translation...
Update classes (set the revision and the count)
where the class is changed in the students table
where that class is in the set of counts of students per class
Well, the main reason your trigger is not fired, is because you are doing an UPDATE where your trigger is going of on a INSERT and DELETE.
Yes, an UPDATE is a DELETE and INSERT, but you have to put it in your footprint :
AFTER DELETE, INSERT, UPDATE
instead of
AFTER DELETE, INSERT
Next, in the trigger itself you only have the internal conceptual tables called inserted and deleted, which combined give you the update. The old part is in the deleted table, the new part is in the inserted table.
Why you want to execute record by record by the way? Doing it with a complete resultset is much faster!