I'm trying to execute the following trigger:
create trigger t23
on studies
after insert, update, delete
as
begin
REFERENCING NEW ROW NewStudent
FOR EACH ROW
WHEN (30 <= (SELECT SUM(credits) FROM Studies)
DELETE FROM NewStudent N
WHERE N.spnr = NewStudent.spnr
end
I'm trying to create a trigger which only inserts a student if the credits is < or == to '30'. The "Credits" is a type int.
I'm getting numerous errors trying to implement this trigger. I really have tried everything and i m out of options. Could someone who is expert in the field point me in the right direction?
The example "Using a DML AFTER trigger to enforce a business rule between the PurchaseOrderHeader and Vendor tables" in the CREATE TRIGGER MSDN documentation does exaclty what you're looking for:
USE AdventureWorks2008R2;
GO
IF OBJECT_ID ('Purchasing.LowCredit','TR') IS NOT NULL
DROP TRIGGER Purchasing.LowCredit;
GO
-- This trigger prevents a row from being inserted in the Purchasing.PurchaseOrderHeader table
-- when the credit rating of the specified vendor is set to 5 (below average).
CREATE TRIGGER Purchasing.LowCredit ON Purchasing.PurchaseOrderHeader
AFTER INSERT
AS
DECLARE #creditrating tinyint, #vendorid int;
IF EXISTS (SELECT *
FROM Purchasing.PurchaseOrderHeader p
JOIN inserted AS i
ON p.PurchaseOrderID = i.PurchaseOrderID
JOIN Purchasing.Vendor AS v
ON v.BusinessEntityID = p.VendorID
WHERE v.CreditRating = 5
)
BEGIN
RAISERROR ('This vendor''s credit rating is too low to accept new purchase orders.', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
The key here is ROLLBACK TRANSACTION, just adapt the example to suit your need and you're done.
Edit: This should accomplish what you're looking for, but I have not tested it so your mileage may vary.
create trigger dbo.something after insert as
begin
if exists ( select * from inserted where sum(credits) > 30 )
begin
rollback transaction
raiserror ('some message', 16, 1)
end
end
Another edit, based on some assumptions (please note I wrote this script on the fly since I can't test it right now):
create table dbo.students
(
student_id int not null,
name varchar (50) not null
)
create table dbo.courses
(
course_id int not null,
name varchar (50) not null,
required_credits int not null
)
create table dbo.results
(
student_id int not null,
course_id int not null,
course_result int not null
)
create trigger dbo.check_student_results on dbo.results after insert as
(
declare #check int
select #check = count(*)
from inserted as a
join dbo.courses as b on b.course_id = a.course_id
where b.required_credits > a.course.result
if #check <> 0
begin
rollback transaction
raiserror('The student did not pass the course.', 16, 1)
end
)
This way when you insert records in the dbo.results table the constraint checks if the student has passed the course, and cancels the insertion if appropriate. However, it's better to check this things in the application layer.
Related
I have a two tables
CREATE TABLE table1 (
ID int not null,
Status int not null,
TimeStamp datetime2(3)
CONSTRAINT pkId PRIMARY KEY (ID)
);
and
CREATE TABLE Status (
StatusID int not null,
StatusName varchar(64) not null
CONSTRAINT pStatus PRIMARY KEY (StatusID)
);
with inserted values
INSERT INTO table1 (ID,StatusID,TimeStamp) VALUES
(1,1,'2021-06-15 07:30:31'),
(2,2,'2021-07-15 07:30:31'),
(3,3,'2021-08-15 07:30:31'),
(4,4,'2021-09-15 08:30:31'),
(5,5,'2021-09-15 07:30:31'),
(6,5,'2021-09-15 07:30:31'),
(7,4,'2021-09-15 07:30:31'),
(8,2,'2021-09-15 07:30:31'),
(9,1,'2021-09-15 07:30:31');
and
INSERT INTO dbo.Status (StatusID,StatusName) VALUES
(1,'wants to enroll'),
(2,'granted enrollment'),
(3,'declined enrollment'),
(4,'attending the course'),
(5,'finished the course');
I wrote a trigger but it prevent all insert when the StatusID < 4 and what I want is to prevent insert into TimeStamp only when the condition is StatusID < 4. I use SQL Server.
CREATE TRIGGER dbo.Prevent_Insert4
ON dbo.table1
FOR INSERT
AS
SET NOCOUNT ON
IF EXISTS(SELECT *
FROM dbo.table1 t
JOIN inserted i ON t.ID=i.ID
WHERE t.StatusID<4)
BEGIN
RAISERROR('You can not update when StatusId value < 4', 16, 1)
ROLLBACK TRAN
SET NOCOUNT OFF
RETURN
END
SET NOCOUNT OFF
Thank you in advance.
Sounds like you just need a CHECK constraint
ALTER TABLE table1
ADD CONSTRAINT CHK_Status
CHECK(Status >= 4 OR TimeStamp IS NULL);
If you really, really wanted to use triggers for this (not recommended) then it should look like this
CREATE OR ALTER TRIGGER dbo.Prevent_Insert4
ON dbo.table1
FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
IF EXISTS(SELECT 1
FROM inserted i
WHERE i.StatusID < 4
AND i.TimeStamp IS NOT NULL)
BEGIN
THROW 50001, 'You can not set TimeStamp when StatusId value < 4', 1;
-- no need for rollback, will happen automatically if you use THROW
END;
go
With your code you are doing rollback of inserted row when condition is met. If I understood you correctly you just to want override timestamp with null value if StatusId < 4.
If so, you should write that instead of rollback
IF EXISTS(SELECT *
FROM dbo.table1 t
JOIN inserted i ON t.ID=i.ID
WHERE t.StatusID<4)
BEGIN
UPDATE t SET t.TimeStamp = NULL
FROM inserted i
JOIN dbo.table1 t ON i.ID = t.ID
WHERE t.StatusID<4
END
I want to know if a certain QuestionID and EmployeeID exist; if they do, they need to be inserted like here below (that's working fine).
But if they don't exist, I want a good error for the user, so that he knows that the QuestionID or the EmployeeID or both do not exist. Also maybe a rollback of the transaction? Now I can add every number, but the stored procedure still completed the commands...
I have this code (I'm using SQL Server):
CREATE PROCEDURE Contentment
#employeeID INT,
#questionid INT
AS
BEGIN
IF EXISTS (SELECT questionid
FROM question
WHERE questionid = #questionid)
IF EXISTS (SELECT employeeID
FROM employee
WHERE employeeid = #employeeID)
BEGIN
INSERT INTO contentment (employeeid, questionid, date, score, comment)
VALUES (#employeeID, #questionid, null, null, null)
END
ELSE
IF #employeeID = 0
RAISERROR ('-certain error-', 16, 1, #employeeid)
IF #questionid = 0
RAISERROR ('-certain error-', 16, 1, #questionid)
END
I'd try to do it like this - check for the existence of both parameters in a single statement - if not, then provide an error:
CREATE PROCEDURE dbo.Contentment
#employeeID INT,
#questionid INT
AS
BEGIN
-- check for *both* conditions at once
IF EXISTS (SELECT * FROM dbo.question WHERE questionid = #questionid) AND
EXISTS (SELECT * FROM dbo.employee WHERE employeeid = #employeeID)
BEGIN
-- if OK --> insert
INSERT INTO dbo.contentment (employeeid, questionid, date, score, comment)
VALUES (#employeeID, #questionid, null, null, null)
END
ELSE
BEGIN
-- if NOT OK --> provide error
RAISERROR ('#EmployeeID or #QuestionID do not exist', 16, 1, null)
END
END
Also maybe a rollback of the transaction?
Since you're not really doing anything (no INSERT happening in either of the two ID's doesn't exist), there's really no transaction to roll back at this point....
you can use an error message parameter like that :
CREATE PROC dbo.Usp_Contentment
AS
begin
declare #ErrMsg NVARCHAR(MAX) = ''
IF not exists (select 1 from dbo.Question where QuestionId= #QuestionId)
set #ErrMsg = 'Some msg'
if not exists (select 1 from dbo.Employee where Employee Id= #EmployeeId)
set #ErrMsg =#ErrMsg + 'some msg'
IF #msg=''
INSERT INTO dbo.contentment (employeeid, questionid, date, score, comment)
VALUES (#employeeID, #questionid, null, null, null)
else
RAISERROR(#ErrMsg,16,1)
end
don't use ROLLBACK because you handled the NULL Values
I would let the database do the work. Declare the table to have proper foreign key relationships:
alter table contentment add constraint fk_conententment_question
foreign key (questionid) references question(questionid);
alter table contentment add constraint fk_conententment_employee
foreign key (employeeid) references employee(employeeid);
Then write the procedure as:
CREATE PROCEDURE dbo.usp_Contentment (
#employeeID INT,
#questionid INT
) AS
BEGIN
INSERT INTO dbo.contentment (employeeid, questionid, date, score, comment)
VALUES (#employeeID, #questionid, null, null, null)
END; -- usp_Contentment
The database will generate an error that the foreign key relationship fails -- if there is no matching key.
This approach is much better than doing the test yourself for several reasons:
The database ensures the data integrity, so you know the relationships are maintained regardless of where the data changes occur.
The integrity applies to both inserts and updates. There is no need to put checks in multiple places.
The integrity check is thread-safe, so if rows are being modified in the reference tables, the integrity check works.
If you really want a customized message, you can catch the error in a try/catch block and have a customized error.
Before anyone suggests a unique index or key, I have a very good case for this.
I am using this trigger, from Trigger to prevent Insertion for duplicate data of two columns:
CREATE TRIGGER LogDuplicates ON bkPersonPoints
FOR INSERT
AS
if exists (select * from bkPersonPoints c
inner join inserted i
on c.Name = i.Name and c.Points = i.Points)
begin
rollback
end
GO
That answer is accepted and has 15 up-votes, so I would expect it to work, yet even on my very first insert, into an empty table:
insert bkPersonPoints (Name, Points) values ('Brady', 100)
I get the error:
The transaction ended in the trigger. The batch has been aborted.
APPENDIX: The table looks like this:
CREATE TABLE [dbo].[bkPersonPoints](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Points] [int] NOT NULL
) ON [APP_BR2_User]
This is happening because it is detecting the record you're currently inserting to the table. You need to filter that out of the EXISTS clause:
CREATE TRIGGER LogDuplicates ON bkPersonPoints
FOR INSERT
AS
if exists (select * from bkPersonPoints c
inner join inserted i
on c.Name = i.Name
and c.Points = i.Points
and c.id <> i.id)
begin
rollback
end
GO
Anyway, suggest CONSTRAINT
ALTER TABLE bkPersonPoints
ADD CONSTRAINT c_uniq_Name_and_points UNIQUE (Name, Points)
This answer was inspired by one posted on Apr 13 '20 at 18:34 in Trigger to prevent Insertion for duplicate data of two columns.
CREATE TRIGGER MyTrigger ON dbo.MyTable
INSTEAD OF INSERT
AS
if not exists (
select * from MyTable t
inner join inserted i
on i.name=t.name and i.date=t.date and i.id <> t.id )
begin
Insert into MyTable (Name, Date) Select Name, Date from inserted
end
else
THROW 51000, 'Statement terminated because a duplicate was found for the object', 1;
go
I have a small little thing with SQL that's been bothering me now for a while, let's say I have two tables (Customer and Loan). However, I want a trigger that's checking based on the Borrowertype attribute. I suppose with the second query after AND I need something to check whether the userID in Loans are the same as the one in Customer, but must be messing it up or I'm completely thinking this the wrong way.
CREATE TABLE Customer
(
userID int identity primary key,
Name varchar(20),
Borrowertype varchar(20)
);
CREATE TABLE Loan
(
Id int identity primary key,
userID int,
FOREIGN KEY (userID) REFERENCES Customer(userID)
);
IF OBJECT_ID ('Customer.maximum_books_per_user','TR') IS NOT NULL
DROP TRIGGER Customer.maximum_books_per_user;
GO
CREATE TRIGGER maximum_books_per_user ON Customer
AFTER INSERT
AS
IF (SELECT Borrowertype FROM Customer) = 'diffborrowertypehere'
AND (SELECT COUNT(*) FROM inserted AS i JOIN Customer AS c
ON ??? WHERE ???
) > 5
BEGIN
ROLLBACK TRANSACTION
RAISERROR('You have reached maximum allowed loans.', 16, 1)
END
GO
Your trigger needs to be on the Loan table, as that's where a row would be being inserted that could be rejected. Something like this:
EDIT: rewritten to handle inserts for multiple Customers at once
CREATE TRIGGER maximum_books_per_user ON Loan
FOR INSERT
AS
-- Fail if there are any customers that will have more than the maximum number of loans
IF EXISTS (
SELECT i.userID, COUNT(*)
FROM inserted i
JOIN Loan l
ON i.userID = l.userID
GROUP BY i.userID
HAVING COUNT(*) >= 5
)
BEGIN
ROLLBACK TRANSACTION
RAISERROR('You have reached maximum allowed loans.', 16, 1)
END
I have two database tables.
tblTeams: PK TeamID,TeamName
tblMatches: PK match id,FK HomeTeam,FK AwayTeam,Score.
I am using SQL Server 2008 and I am importing rows through the Wizard from a .csv file. The columns in the csv are hometeam,awayteam,score. Thus, before inserting in the tblMatches, I want a trigger that finds the FK of the team and inserts in the tblMatches the foreign key and not the name.
Any help with that please.
CREATE TRIGGER tblmatches_BeforeInsert
ON tblmatches
BEFORE INSERT
AS
BEGIN
INSERT tblmatches
SELECT teamName
FROM tblmatches
WHERE tblTeams.id = ?i dont know here what to insert?
END
If you want a different representation in the table then you'll probably have to implement a view, and perform the inserts via it, rather than the base table.
Something like:
CREATE TABLE realMatches (
MatchID int IDENTITY(1,1) not null, /* Identity? */
HomeTeamID int not null,
AwayTeamID int not null,
Score int not null, /* int? */
constraint PK_realMatches PRIMARY KEY (MatchID),
constraint FK_Matches_HomeTeams (HomeTeamID) references tblTeams (TeamID),
constraint FK_Matches_AwayTeams (AwayTeamID) references tblTeams (TeamID)
)
GO
CREATE VIEW tblMatches
AS
SELECT
MatchID,
ht.TeamName as HomeTeam,
at.TeamName as AwayTeam,
Score
FROM
realMatches m
inner join
tblTeams ht
on
m.HomeTeamID = ht.TeamID
inner join
tblTeams at
on
m.AwayTeamID = at.TeamID
GO
CREATE TRIGGER T_Matches ON tblMatches
INSTEAD OF INSERT
AS
SET NOCOUNT ON
INSERT INTO realMatches (HomeTeamID,AwayTeamID,Score)
SELECT ht.TeamID,at.TeamID,i.Score
FROM
inserted i
inner join
tblTeam ht
on
i.HomeTeam = ht.TeamName
inner join
tblTeam at
on
i.AwayTeam = at.TeamName
You can now (assuming that "A Team" and "Team America" exist in tblTeams):
INSERT INTO tblMatches (HomeTeam,AwayTeam,Score)
VALUES ('A Team','Team America',19)
Of course, this doesn't (yet) deal with any updates which attempt to change a team in the matches table, nor what to do if a team doesn't yet exist in tblTeam, but you've not asked about those yet.
AFAIK, for a trigger to be called on tblMatches, you need to supply values for all the columns in that table and only for the columns in that table which means, you cannot pass a team name to a trigger so that it can be used to resolve the team ID.
If I were to do it, I would just create a table to insert the raw data as is, let the import wizard write records into this table, define a trigger on this table to insert records into the two derived tables viz. tblTeams, tblMatches. (SQL Server Triggers)
My attempt at writing one (didn't get a chance to verify as I don't have SQL server)
CREATE TRIGGER teams.process ON teams
AFTER INSERT
AS
BEGIN
DECLARE #homeTeamId INT
DECLARE #awayTeamId INT
DECLARE #maxTeamId INT
DECLARE #matchId INT
SELECT #maxTeamId = 0
SELECT #maxTeamId = ISNULL(MAX(teamId), 0) from tblTeams
--- Check if home team has already been inserted into the table.
SELECT #homeTeamId = -1
SELECT
#homeTeamId = teamId
FROM
tblTeams t
JOIN inserted i
ON t.teamName = i.hometeam
IF (#homeTeamId = -1)
BEGIN
SELECT #homeTeamId = #maxTeamId + 1
SELECT #maxTeamId = #maxTeamId + 1
INSERT INTO tblTeams SELECT #homeTeamId, i.hometeam FROM inserted i
END
--- Check if away team has already been inserted into the table.
SELECT #awayTeamId = -1
SELECT
#awayTeamId = teamId
FROM
tblTeams t
JOIN inserted i
ON t.teamName = i.awayteam
IF (#awayTeamId = -1)
BEGIN
SELECT #awayTeamId = #maxTeamId + 1
SELECT #maxTeamId = #maxTeamId + 1
INSERT INTO tblTeams SELECT #awayTeamId, i.awayteam FROM inserted i
END
-- insert a record into the matches table with the home team ID and away team ID.
SELECT #matchId = 0
SELECT #matchId = ISNULL(MAX(MatchId), 0) FROM tblMatches
INSERT INTO tblMatches
SELECT #matchId + 1, #homeTeamId, #awayTeamId, i.score
FROM inserted i
END