DELETE statement conflicted with Reference constraints in SQL Stored Procedure - sql

My Employee table has the Primary key Column that is (EmpID) which is referenced in two Other tables
EmployeeEducation table and EmployeeBankInformation table.
I want to Delete Employee against their ID but as I run the Stored Procedure my controller throws the error that the foreign key reference constraint conflicts because of the EmpID column.
ALTER PROCEDURE [dbo].[RemoveEmployee]
(
#EmpID int
)
AS
BEGIN
DELETE FROM Employee WHERE EmpID = #EmpID
DELETE FROM EmployeeEducation WHERE EmpID = #EmpID
DELETE FROM EmployeeBankInformation WHERE EmpID = #EmpID
END
Where am I going wrong? Can any of you tell what mistake I am making in my procedure?

You are very close, just need to remove the employee last:
ALTER PROCEDURE [dbo].[RemoveEmployee]
(
#EmpID int
)
AS
BEGIN
DELETE FROM EmployeeBankInformation WHERE EmpID = #EmpID
DELETE FROM EmployeeEducation WHERE EmpID = #EmpID
DELETE FROM Employee WHERE EmpID = #EmpID
END

Related

SQL Inserted table trigger

If I run the following select statement inside an insert trigger, is it possible that it will return more than one result?:
DECLARE #variable char(1) = (SELECT ID FROM inserted)
If so, then what's the best to handle it?
Here is the problem that I am trying to solve: Every time when the new record is inserted into a table, I want to take the newly inserted ID and insert it into another table(if it doesn't exists).
Thank you!
Instead of
DECLARE #variable char(1) = (SELECT ID FROM inserted)
You can do something like following:
Declare #VarTempTbl as table (id int)
Insert into #VarTempTbl (id)
Select id from inserted
So that you can get those values for further processing
Now, I had created Two tables One for Master table and another for When any Insertion happens in that Master table, that entry has to inserted into the another table.
CREATE TABLE tblEmployee
(
Id int Primary Key,
Name nvarchar(30),
Gender nvarchar(10),
DepartmentId int
)
CREATE TABLE tblEmployee_New
(
Id int Primary Key,
Name nvarchar(30),
Gender nvarchar(10),
DepartmentId int
)
Trigger:
CREATE TRIGGER TR_EMPLOYEEDETAILS_AFTEROFINSERT
ON TBLEMPLOYEE
AFTER INSERT
AS
BEGIN
TRUNCATE TABLE tblEmployee_New
INSERT INTO TBLEMPLOYEE_NEW(ID, NAME, GENDER, DEPARTMENTID)
SELECT ID, NAME, GENDER, DEPARTMENTID
FROM INSERTED
END
Now Lets try to insert into record into a master table
Insert into tblEmployee values (1,'John', 'Male', 3)
Insert into tblEmployee values (2,'Mike', 'Male', 2)
It has automatically insert the newly inserted records into the another table.
If your want to remove the Previous records then add a drop Statement in that above Trigger.
Note: You can also use #Temp Table instead of creating a another table('tblEmployee_New')
Kinldy Share your comments

How to declare in stored procedure that a row does not exist?

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.

Oracle SQL: Invalid table name when creating a trigger

I created this 4 tables:
create table terminatedEmployees (
empid number primary key,
dept number,
empname varchar2(50),
salary number
);
create table employees (
empid number primary key,
dept number,
empname varchar2(50),
salary number
);
create table payroll (
empid number primary key,
salary number,
CONSTRAINT fk_payemploy
FOREIGN KEY (empid)
REFERENCES employees(empid)
);
create table salaryAudit (
empid number primary key,
oldsal number,
newsal number,
datechanged date,
changedby varchar2(25),
CONSTRAINT fk_salaryaudit
FOREIGN KEY (empid)
REFERENCES employees(empid)
);
and now I'm trying to create a trigger in order to update two of them when employees table is updated:
CREATE TRIGGER trigger_updated_employees
AFTER UPDATE ON employees
FOR EACH ROW
when (old.salary != new.salary)
BEGIN
UPDATE INTO salaryAudit (newsal, oldsal)
VALUES(:new.salary, :old.salary);
UPDATE INTO payroll (salary)
VALUES(:new.salary);
END;
But I'm getting the error:
2/5 PL/SQL: SQL Statement ignored
2/12 PL/SQL: ORA-00903: invalid table name
4/5 PL/SQL: SQL Statement ignored
4/12 PL/SQL: ORA-00903: invalid table name
The three tables I'm calling in the trigger are ok and other triggers I created work...
Try something like this:
CREATE TRIGGER TRIGGER_UPDATED_EMPLOYEES
AFTER UPDATE ON EMPLOYEES
FOR EACH ROW
WHEN (OLD.SALARY <> NEW.SALARY)
BEGIN
MERGE INTO PAYROLL p
USING (SELECT :NEW.EMPID AS EMPID FROM DUAL) d
ON (p.EMPID = d.EMPID)
WHEN NOT MATCHED THEN
INSERT (EMPID, SALARY)
VALUES (:NEW.EMPID, :NEW.SALARY)
WHEN MATCHED THEN
UPDATE
SET SALARY = :NEW.SALARY;
MERGE INTO SALARYAUDIT a
USING (SELECT :NEW.EMPID AS EMPID FROM DUAL) d
ON (a.EMPID = d.EMPID)
WHEN NOT MATCHED THEN
INSERT (EMPID, OLDSAL, NEWSAL, DATECHANGED, CHANGEDBY)
VALUES (:NEW.EMPID, :OLD.SALARY, :NEW.SALARY, SYSDATE, 'SOME_USER')
WHEN MATCHED THEN
UPDATE
SET OLDSAL = :OLD.SALARY,
NEWSAL = :NEW.SALARY,
DATECHANGED = SYSDATE,
CHANGEDBY = 'SOME_USER';
END TRIGGER_UPDATED_EMPLOYEES;
Share and enjoy.
I got it working just correcting the UPDATE statements syntax and modifying the condition as #BobJarvis suggested, this is the final result:
CREATE TRIGGER trigger_updated_employees
AFTER UPDATE OF salary ON employees
FOR EACH ROW
when (old.salary <> new.salary)
BEGIN
UPDATE salaryAudit
SET (newsal, oldsal)
VALUES (:new.salary, :old.salary)
WHERE (salaryAudit.empid = old.empid);
UPDATE payroll
SET (salary)
VALUES (:new.salary)
WHERE (payroll.empid = old.empid);
END;
The concept is the same that #BobJarvis proposed but a lot simpler. Thanks

Creating a status update trigger

I have 2 tables like so:
JOBS table
Jobcode UserId Status
101 130 R
102 139 D
USERS table
UserId Email
130 test#example.com
I want to create a trigger on insert and update that sends an email to my stored procedure:
EXEC dbo.SendMyEmail #email, #jobcode;
when the jobcode is inserted as 'D' or updated to 'D'.
In my opinion, sending email in a trigger is not optimal.
Instead, you should just insert to a queue table, and have a process run frequently that checks the table and sends the email.
What happens if you get an error in your email procedure? It will force a rollback of your job completion status. Only you know whether that is minor or possibly catastrophic. But I can tell you for sure that DB best practice is to NOT do extended I/O during a DML operation.
CREATE TRIGGER TR_Jobs_EnqueueEmail_IU ON dbo.Jobs FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
INSERT dbo.EmailQueue (UserID, JobCode)
SELECT UserID, JobCode
FROM
Inserted I
LEFT JOIN Deleted D
ON I.JobCode = D.JobCode -- or proper PK columns
WHERE
IsNull(D.Status, 'R') <> 'D'
AND I.Status = 'D';
Tables needed:
CREATE TABLE dbo.EmailQueue (
QueuedDate datetime NOT NULL
CONSTRAINT DF_EmailQueue_QeueueDate DEFAULT (GetDate()),
UserID int NOT NULL,
JobCode int NOT NULL,
CONSTRAINT PK_EmailQueue PRIMARY KEY CLUSTERED (QueuedDate, UserID, JobCode)
);
CREATE TABLE dbo.EmailSent (
SentDate datetime NOT NULL
CONSTRAINT DF_EmailSent_SentDate DEFAULT (GetDate()),
QueuedDate datetime NOT NULL,
UserID int NOT NULL,
JobCode int NOT NULL,
CONSTRAINT PK_EmailSent PRIMARY KEY CLUSTERED (SentDate, QueuedDate, UserID, JobCode)
);
Then, run the following stored procedure once a minute from a SQL Job:
CREATE PROCEDURE dbo.EmailProcess
AS
DECLARE #Email TABLE (
QueuedDate datetime,
UserID int,
JobCode int
);
DECLARE
#EmailAddress nvarchar(255),
#JobCode int;
WHILE 1 = 1 BEGIN
DELETE TOP 1 Q.*
OUTPUT Inserted.QueuedDate, Inserted.UserID, Inserted.JobCode
INTO #Email (QueuedDate, UserID, JobCode)
FROM dbo.EmailQueue Q WITH (UPDLOCK, ROWLOCK, READPAST)
ORDER BY QueuedDate;
IF ##RowCount = 0 RETURN;
SELECT #EmailAddress = U.EmailAddress, #JobCode = E.JobCode
FROM
#Email E
INNER JOIN dbo.User U
ON E.UserID = U.UserID;
EXEC dbo.SendMyEmail #EmailAddress, #JobCode;
DELETE E
OUTPUT QueuedDate, UserID, JobCode
INTO dbo.EmailSent (QueuedDate, UserID, JobCode)
FROM #Email E;
END;
The delete pattern and locks I used are very specifically chosen. If you change them or change the delete pattern in any way it is almost certain you will break it. Handling locks and concurrency is hard. Don't change it.
Note: I typed all the above without checking anything on a SQL Server. It is likely there are typos. Please forgive any.
I'm not sure about data types etc but this should at least put you on the right track.
Hope it helps...
CREATE TRIGGER SendEmailOnStatusD
ON JOBS
-- trigger is fired when an update is made for the table
FOR UPDATE --You can add the same for INSERT
AS
-- holds the UserID so we know which Customer was updated
DECLARE #UserID int
DECLARE #JobCode int
SELECT #UserID = UserId, #JobCode = JobCode
FROM INSERTED WHERE [Status] = 'D' --If you want the old value before the update, use 'deleted' table instead of 'inserted' table
IF (#UserID IS NOT NULL)
BEGIN
-- holds the email
DECLARE #email varchar(250)
SELECT #email = Email FROM USERS WHERE UserId = #UserID
EXEC SendMyEmail (#email, #jobcode);
END
GO
EDIT:
Above code does not handle multiple updates, so for better practice see below option
CREATE TRIGGER SendEmailOnStatusD ON JOBS
-- trigger is fired when an update is made for the table
FOR UPDATE --You can add the same for INSERT
AS
DECLARE #Updates table(UserID int, JobCode int, Email varchar(250))
INSERT INTO #Updates (UserID, JobCode, Email)
SELECT i.UserID, i.JobCode, u.Email
FROM INSERTED i
JOIN USERS u ON i.UserID = u.UserID
WHERE [Status] = 'D'
DECLARE #UserID int
DECLARE #JobCode int
DECLARE #Email varchar(250)
WHILE EXISTS(SELECT * FROM #Updates)
BEGIN
SELECT TOP 1
#UserID = UserID,
#Email = Email,
#JobCode = JobCode
FROM #Updates WHERE UserID = #UserID
EXEC SendMyEmail (#email, #jobcode);
DELETE FROM #Updates
WHERE UserID = #UserID
END
GO
Additionally, as discussed in the comments, sending emails from a trigger is also not the best, but as this is what the question asks for it has been included. I would recommend alternative options for sending emails such as a queue which has been mentioned in other answers.

SQL Trigger Recursive update

I have an organizational database where in it each employee have a foreign key to it's boss(FID).
Table declaration:
Create Table Emp(
ID integer,
FID integer,
SALARY integer,
Primary key (ID),
foreign key (FID) references EMP
);
The following sql trigger should update the employees under a boss. And then update their children recursively. but it only updates one level.
CREATE TRIGGER SAL_TRIG ON EMP After UPDATE
as
declare #SALARY int
declare #OLDSAL int
declare #ID int
--use the 'inserted' keyword to access the values inserted into the invoice table
select #OLDSAL = Salary from deleted
select #SALARY = Salary from inserted
select #ID = ID from inserted
BEGIN
UPDATE EMP
SET SALARY = salary + #SALARY - #OLDSAL
WHERE FID = #ID
END
I want to know how to solve this problem.
Thank you.
Assuming this is SQL Server, you need to enable recursive triggers using sp_dboption. See MSDN for more details:
http://msdn.microsoft.com/en-us/library/ms189799.aspx