Create Trigger to update insert to table specific values only - sql-server

I need to create trigger prevent insert and update to table employee under age 21 and over age 67
what next on the code?
CREATE TRIGGER allowInsertUpdateemployee ON dbo.employee
AFTER UPDATE, INSERT
AS
BEGIN
DECLARE #v_ageLow INT = 21,
#v_ageHigh INT = 67,
#v_dateLow date,
#v_dateHigh date
SET #v_dateLow = DATEADD(YEAR, -1 * #v_ageLow, GETDATE())
SET #v_dateHigh = DATEADD(YEAR, -1 * #v_ageHigh, GETDATE())
END

Since the upper and lower bounds are fixed, a check constraint might be a more appropriate solution than a trigger
ALTER TABLE employee ADD CONSTRAINT ck_employee_age CHECK
(DateOfBirth BETWEEN DATEADD(YEAR,-67,GETDATE()) AND DATEADD(YEAR,-21,GETDATE()))

Use "INSTEAD OF INSERT, UPDATE" trigger;
Use INSERTED table to check new incoming values, raiserror if needed;
Use DELETED table to detect if update is processing (this can
help);
Do manual insert or update then (if needed).
INSERT INTO
dbo.employee
SELECT
*
FROM
INSERTED I

I hope this will do
This will not fetch accurate age , However if your method gets you an accurate date you can use your code with it .
OR
you can also use below code to get age:
SELECT DATEDIFF(Day,'2011-11-03' , GETDATE()) /365
SELECT DATEDIFF(Day,#Age , GETDATE())
/365
CREATE TRIGGER allowInsertUpdateemployee ON dbo.employee
INSTEAD OF Insert
AS
BEGIN
DECLARE #v_ageLow INT,
#v_ageHigh INT,
#v_date date,
--#v_dateHigh date
SET #v_ageLow = SELECT DATEDIFF(Year,#v_date , GETDATE())
SET #v_ageHigh = SELECT DATEDIFF(Year,#v_date , GETDATE())
BEGIN
if(#v_ageLow <21 AND #v_ageHigh >67)
BEGIN
RAISERROR('Cannot Insert or Update where User is not in age limit);
ROLLBACK;
END
ELSE
BEGIN
// else write your insert update query
Insert into values ()
PRINT 'Unable to Insert-- Instead Of Trigger.'
END
END

You have to put OR in the where clause. The employee can't be under 21 AND more than 67.
Create TRIGGER tr_Too_young_or_too_old
ON TableName
AFTER INSERT
AS
if exists ( select *, DATEDIFF(yy, birthdate, GETDATE()) as age
from TableName
where DATEDIFF(yy, birthdate, GETDATE()) < 21 or DATEDIFF(yy, birthdate, GETDATE()) > 67 )
begin
rollback
RAISERROR ('Cannot Insert or Update where User is not in age limit', 16, 1);
end

Related

Trigger to record changes to an employee

I am trying to create a SQL Server trigger.
When I update the number of the department (dept_no) or the salary in the Employees table, it will insert a new entry into the changes table with the EMP_NO, the old and the new salary, the previous and the new department and the current date.
I tried this trigger code, but it is not working.
ALTER TRIGGER MODEMPLE
ON EMPLOYEES
FOR UPDATE
AS
BEGIN
DECLARE #n_dept_no varchar(4),
#n_salary money,
#a_salary money,
#emp_no int,
#a_dept_no int
SELECT #n_dept_no = dept_no FROM inserted
SELECT #n_salary = salary FROM inserted
SELECT #a_salary = SALARY FROM deleted
SELECT #emp_no = EMP_NO FROM deleted
SELECT #a_dept_no = DEPT_NO FROM deleted
INSERT INTO CHANGES (EMP_NO, A_SALARY, N_SALARY, A_DEPT_NO, N_DEPT_NO, DATE)
VALUES (#emp_no, #a_salary, #n_salary, #a_dept_no, #n_dept_no, GETDATE())
END
Get rid of all the variables, they make no sense in this context because you need to handle multi-row updates. So just:
ALTER TRIGGER dbo.MODEMPLE
ON dbo.EMPLOYEES FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.CHANGES
(EMP_NO, A_SALARY, N_SALARY, A_DEPT_NO, N_DEPT_NO, DATE)
SELECT [old].EMP_NO,
[old].SALARY, [new].salary,
[old].DEPT_NO, [new].DEPT_NO, GETDATE()
FROM inserted AS [new]
INNER JOIN deleted AS [old]
ON [new].EMP_NO = [old].EMP_NO;
END
GO
You also may want to filter out rows where the update didn't make a material change (like UPDATE dbo.EMPLOYEES SET Salary += 0;) but that's beyond the scope of this question.

I'm getting an error message but it exec fine?

IF EXISTS (SELECT name
FROM sys.tables
WHERE name = 'Nums')
BEGIN
DROP TABLE dbo.Nums;
END
CREATE TABLE dbo.Nums
(
number INT NOT NULL,
CONSTRAINT PK_Nums PRIMARY KEY CLUSTERED(number ASC),
code Char(9),
date DATETIME
) ON [PRIMARY]
INSERT INTO Nums (number, code, date)
VALUES (0, 485658235, '2000/01/01')
DECLARE #number int, #code Char(9), #date datetime
SET #number = (SELECT MAX (Number) FROM nums)
SET #date = (SELECT Date FROM Nums)
SET #code = (SELECT code FROM Nums)
WHILE #number < 100000
BEGIN
INSERT INTO Nums--(number, code, date)
VALUES (#number, #code, #date)
SET #number = #number + 1
SET #date = DATEADD(DAY, 5, #date)
SET #code = LEFT(CAST(CAST(CEILING(RAND()* 10000000000) AS bigint) AS varchar),9)
END
SELECT * FROM Nums
This is the error I get:
Msg 2627, Level 14, State 1, Line 41
Violation of PRIMARY KEY constraint 'PK_Nums'. Cannot insert duplicate key in object 'dbo.Nums'. The duplicate key value is (0)
You need to add 1 to the number before the loop starts.
The error Violation of PRIMARY KEY constraint is not normally batch-aborting, therefore execution will continue with the next statement.
If you want to stop that, either force the batch to abort and rollback: SET XACT_ABORT ON (probably a good idea), or use TRY/CATCH.
I assume you also realize your procedure can be done in one statement, by joining a tally table or function (for example these by Itzik Ben-Gan), something like this:
INSERT #Nums (number, code, date)
SELECT
t.Num,
LEFT(CAST(CAST(CEILING(RAND()* 10000000000) AS bigint) AS varchar),9),
'2000/01/01'
FROM fnTallyTable (0, 99999) t

After Insert Trigger using the values from the INSERT

I have the following query:
INSERT INTO LOAN (MemberSSN, VolumeID, LoanDate)
OUTPUT inserted.MemberSSN, inserted.VolumeID, inserted.LoanDate
VALUES ('488-40-', 2, GETUTCDATE())
I want a trigger that, upon insertion in the LOAN table, the following query is executed - an INSERT into the VOLUME_VOLUME_STATUS table:
INSERT INTO VOLUME_VOLUME_STATUS (VolumeID, StatusID, DateCreated)
VALUES (>>previouslyInsertedVolumeID<<, 2, getutcdate())
As you can see, I am unsure how to tell SQL the VolumeID that has just been inserted, which is 2.
Below is the trigger that I wrote:
CREATE TRIGGER LoanStatusUpdate_OnLoaning
AFTER INSERT
ON LOAN
BEGIN
INSERT INTO VOLUME_VOLUME_STATUS (VolumeID, StatusID, DateCreated)
VALUES (>>previouslyInsertedVolumeID<<, 2, getutcdate())
END
Therefore, what should be passed as the value for the VolumeID column so that the parameter is the one from the INSERT prior to the trigger being activated?
Figured some errors in the previous trigger. Here's the one with fixed syntax errors:
CREATE TRIGGER LoanStatusUpdate_OnLoaning
ON LOAN
AFTER INSERT
AS
INSERT INTO VOLUME_VOLUME_STATUS (VolumeID, StatusID, DateCreated)
VALUES (New.VolumeID, 2, getutcdate())
GO
I think you just want to use inserted:
CREATE TRIGGER LoanStatusUpdate_OnLoaning ON LOAN AFTER INSERT
BEGIN
INSERT INTO VOLUME_VOLUME_STATUS (VolumeID, StatusID, DateCreated)
SELECT i.VolumeId, 2, getutcdate()
FROM inserted i;
END;
You can also pull the StatusId from inserted as well. And DateCreated could be set as a default value in VOLUME_VOLUME_STATUS.

Stored Procedure with two raiserrors

I use SQL Server.
I want to write a stored procedure that looks if a questionid and employeeid exists (questionid is in the table question, same for employeeid, is in the table employee) AND looks if they not already existing in the table (you don't get a duplicate in the table contentment). I want raiserror's for the user if so.
In my case it is possible to have a duplicate but not on the same DATE!
Contentment table has columns:
employeeid, questionid, date, score
employeeid, questionid, date make up the primary key.
So I want something like this:
1,1, 18-11-2018, null
1,1, 19-11-2018, null
and not something like this:
1,1, 18-11-2018, null
1,1, 18-11-2018, null
I already made something but it is not working (1-1-1900 is a standard date, because it is primary key it needs to be inserted, score is not inserted because the user needs to do this):
#employeeid int,
#questionid int
as
begin
if exists (select * from question where questionid = #questionid)
and exists (select * from employee where employeeid= #employeeid)
begin
insert into contentment (employeeid, questionid, date, score)
values (#employeeid, #questionid, '1-1-1900', null)
end
if (select count(*)
from contentment
where employeeid = #employeeid
and questionid = #questionid
and date = date) = 0
raiserror ('#employeeid or #questionid already existing', 16, 1)
else
raiserror ('#employeeid or #questionid are not existing', 16, 1, null)
end
If you want to date validation you need to provide #date as well. I have created a sample stored procedure with detail that you provide:
DROP PROCEDURE P_ContentmentInsert
GO
CREATE PROCEDURE P_ContentmentInsert
#employeeid int,
#questionid int,
#date DATE
AS
BEGIN
--Check if exists Employee, Question and Contentment
DECLARE #IsEmployeeExists INT=ISNULL((SELECT COUNT(1) FROM employee WHERE employeeid= #employeeid),0)
DECLARE #IsQuestionExists INT=ISNULL((SELECT COUNT(1) FROM question WHERE questionid = #questionid),0)
DECLARE #IsContentmentExists INT=ISNULL((SELECT COUNT(1) FROM contentment WHERE questionid = #questionid and employeeid= #employeeid and [date]=#date),0)
DECLARE #ErrorMessage VARCHAR(1000)=''
--If one of the validation not passed give error message
IF (#IsEmployeeExists=0 OR #IsQuestionExists=0 OR #IsContentmentExists=0)
BEGIN
IF #IsEmployeeExists=0
SET #ErrorMessage='-EmployeeId Not exists'
IF #IsQuestionExists=0
SET #ErrorMessage=#ErrorMessage+'-QuesitonId Not exists'
IF #IsContentmentExists=0
SET #ErrorMessage=#ErrorMessage+'-Contentment already exists'
RETURN
END
--If there is no problem insert it.
IF #IsEmployeeExists>0 and #IsQuestionExists>0 and #IsContentmentExists>0
BEGIN
INSERT INTO contentment (employeeid, questionid, date, score)
VALUES (#employeeid, #questionid, #date, null)
END
END

How to properly populate the age attribute?

I have an MS-SQL Server which keeps track on a number of clients. If the birthday is known it is stored as a datetime attribute called dayOfBirth. Now I would like to have another attribute age, which keeps track of the current age of the client. Since age can change any day I figured a script might be the best idea.
First thing I did, was to create a stored procedure which computes the age given the birthday as datetime. Here is what I came up with:
USE [MyDB]
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE CalculateAge
#dayOfBirth datetime,
#age INT OUTPUT
AS
DECLARE #today datetime, #thisYearBirthDay datetime
DECLARE #years int
SET #today = GETDATE()
SET #thisYearOfBirth = DATEADD(year, #dayOfBirth, #today), #dayOfBirth)
SET #years = DATEDIFF(year, #dayOfBirth, #today) - (CASE WHEN #thisYearBirthDay > #today THEN 1 ELSE 0 END)
SET #age = #years
Afterwards I created another script which runs through all records that have a non null dayOfBirth attribute and updates the age filled accordingly.
USE [MyDB]
GO
DECLARE #age int;
DECLARE #birth datetime;
DECLARE #id intl
DECLARE cursorQuery CURSOR FOR SELECT clientId FROM Clients WHERE dayOfBirth IS NOT NULL;
OPEN cursorQuery
FETCH NEXT FROM cursorQuery INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
SET #birth = (SELECT dayOfBirth from Kunden where clientId=#id);
EXEC dbo.CalculateAge #birth, #age OUTPUT;
UPDATE Clients SET Age = #age WHERE clientId = #id;
FETCH NEXT FROM cursorQuery INTO #id
END
CLOSE cursorQuery
DEALLOCATE cursorQuery
I would trigger the script above once per day to populate the age attribute. Thats what I have so far, but I have the feeling there is plenty of room for improvement.
** Thanks Sung Meister **
I ended up with something like this:
CREATE TABLE Client (
ID int identity(1,1) primary key,
DOB datetime null,
AGE as (case
when DOB is null then null
else DATEDIFF(YY,DOB,GETDATE()) - CASE WHEN (MONTH(GETDATE()) = MONTH(DOB) AND DAY(DOB) > DAY(GETDATE()) OR MONTH(GETDATE()) > MONTH(DOB)) THEN 1 ELSE 0 END
end
)
)
Instead of creating a trigger and do any manual updates,
easiest way to get around with this is to create a calculated field called Age.
This way, you do not have to worry about Age data being out of sync (data stays consistent) and no more trigger or manual update is required.
Calculating an age of person can be a bit hairy as you can see in the picture below.
Here is the text used
create table #Person (
ID int identity(1, 1) primary key,
DOB datetime null,
Age as
(case
when DOB is null then null
--; Born today!
when datediff(d, DOB, getdate()) = 0 then 0
--; Person is not even born yet!
when datediff(d, DOB, getdate()) < 0 then null
--; Before the person's B-day month so calculate year difference only
when cast(datepart(m, getdate()) as int) > cast(datepart(m, DOB) as int)
then datediff(year, DOB, getdate())
--; Before Person's b-day
else datediff(year, DOB, getdate()) - 1
end)
)
insert #Person select GetDate()
insert #Person select null
insert #Person select '12/31/1980'
select * from #Person
update #Person
set DOB = '01/01/1980'
where ID = 2
select * from #Person
You really shouldn't be storing the age within the database as it is easily calculated and changes on a daily basis.
I would suggest that you keep the date of birth field and just calculate the age as you need it. If you wish to have the age selected along with the other attributes then consider a view, perhaps with a user defined function to calculate the age.
The following is an example (untested) UDF that you could use
CREATE FUNCTION age
(
#userId int
)
RETURNS int
AS
BEGIN
DECLARE #Result int
SELECT #Result = DATEDIFF(year, dateofBirth, getDate())
FROM person
WHERE userId = #userId
RETURN #Result
END
GO
Then within your queries you can do something similar to the following,
SELECT *,
dbo.age(userId) as age
FROM person
In answer to your question on sorting etc, then you could create a view on the data and use that to show the data so something like this (untested)
CREATE VIEW personview(firstname, surname, dateOfBirth,age) AS
SELECT firstname,
surname,
dateOfbirth,
dbo.age(userid)
FROM person
You can then use this view to perform your queries, there could be a performance hit for filtering and sorting based on the age and if you regularly sort/filter based upon the age field then you may want to create an indexed view.
Use a view and/or function. Never store two fields that arew based on the exact same data if you can help it as they will eventually get out of sync.

Resources