I'm working on a small database in SQL Server 2008 to track employee changes. I'm having trouble with an Insert Trigger at the moment. What I want to happen, is that when a "Record" is inserted into the Record table, it finds the previous open record (i.e. one without an end date) for that Employee (EmpID), if there is one, and updates it with an EndDate - which will be calculated as the day before the inserted StartDate. Here is what I have tried, to no success:
CREATE TRIGGER [dbo].[trgInsertRecord] ON [dbo].[Record]
FOR INSERT
AS
declare #EmpID int;
declare #StartDate date;
declare #EndDate date;
select #EmpID=i.EmpID from inserted i;
select #StartDate=i.RealStart from inserted i;
set #EndDate=DATEADD(DAY,-1,#StartDate)
UPDATE Record
SET RealEnd=#EndDate
WHERE EmpID=#EmpID AND RealEnd=NULL;
Can somebody please help me understand my mistake?
Assuming only a single row exists for a given EmpID and NULL RealEnd, you can join to the inserted table for the update operation like the example below. The best practice is to code triggers as to handle multiple rows updated by a single statement so you should avoid local scalar subqueries in triggers.
CREATE TRIGGER [dbo].[trgInsertRecord] ON [dbo].[Record]
FOR INSERT
AS
UPDATE r
SET RealEnd = DATEADD(DAY, -1, i.RealStart)
FROM Record AS r
JOIN inserted AS i ON
r.EmpID=i.EmpID
AND RealEnd IS NULL;
Related
I'm creating a table and setting up a trigger to insert/update field_3 using field_1 and 2. See my example below.
ID | Start_Date | End_Date | Period |
1 3/10/17 3/20/17 10
2 2/05/17 3/10/17 5
Trigger
ALTER TRIGGER [dbo].[tr_insert_Period]
ON [dbo].[MEDICAL_EXCUSE]
for update
AS
BEGIN
SET NOCOUNT ON;
declare #Start_Date Datetime
declare #End_Date Datetime
declare #Period Float
set #Start_Date = ( select me_start_date from inserted)
set #End_Date = ( select ME_End_Date from inserted)
set #Period = ( select Period from inserted)
update MEDICAL_EXCUSE
set #Period = DATEDIFF(day,#Start_Date , #End_Date)
END
it won't trigger just work if you execute Manually.
Any help Much Appreciated.
Don't use a trigger. Just use a computed column. I think this is what you want:
alter table KOPS_MEDICAL_EXCUSE
add period as (DATEDIFF(day, me_start_date, me_end_date));
Note that datediff() returns an integer, so there is no reason to declare anything as a float.
With a computed field, the value is usually calculated when you query the table. You can materialize the value if you want using the PERSISTED keyword.
Several issues I see with your trigger. First you can never assume only one record is going to be updated ever. Enterprise databases (typically the only kind complex enough to need triggers) often are accessed outside the user application and a large insert might appear.
Next, you want to get the records from an insert, so it needs to be an insert trigger. And finally you are updating the value of the variable #period instead of the field Period in your table.
Try this:
ALTER TRIGGER [dbo].[tr_insert_Period]
ON [dbo].[MEDICAL_EXCUSE]
for insert
AS
BEGIN
UPDATE ME
SET Period = DATEDIFF(day,i.Start_Date , i.End_Date)
FROM MEDICAL_EXCUSE ME
JOIN Inserted i on i.id = me.id
END
You might also want this to work for updates as well, so then make it:
FOR Insert, update
I wrote this trigger to insert data into another table after data insert into the CHECKINOUT table.
But it doesn't insert data into the Att_process table. No errors is showing up in SQL Server. Can you help me to figure this out problem?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[trgAfterInsert] ON [dbo].[CHECKINOUT]
AFTER INSERT
AS
DECLARE #uid INT;
DECLARE #checkin DATETIME;
SELECT #uid = i.USERID
FROM [CHECKINOUT] i;
SELECT #checkin = i.CHECKTIME
FROM [CHECKINOUT] i;
IF(DATEPART(HOUR, #checkin) < 12)
BEGIN
INSERT INTO Att_process (USERID, checkin_time)
VALUES (#uid, #checkin);
END;
ELSE
BEGIN
INSERT INTO Att_process (USERID, checkout_time)
VALUES (#uid, #checkin);
END;
Two main problems:
you're not looking at the Inserted pseudo table which contains the newly inserted rows
you're assuming the trigger is called once per row - this is not the case, the trigger is called once per statement and the Inserted pseudo table will contain multiple rows - and you need to deal with that
So try this code instead:
CREATE TRIGGER [dbo].[trgAfterInsert] ON [dbo].[CHECKINOUT]
AFTER INSERT
AS
-- grab all the rows from the "Inserted" pseudo table
-- and insert into the "checkin_time" column, if the value
-- of the HOUR is less than 12
INSERT INTO Att_process (USERID, checkin_time)
SELECT
i.USERID, i.CHECKTIME
FROM
Inserted i
WHERE
DATEPART(HOUR, i.CHECKTIME) < 12
-- grab all other rows (HOUR is greater than or equal to 12)
-- and insert into the "checkout_time" column
INSERT INTO Att_process (USERID, checkout_time)
SELECT
i.USERID, i.CHECKTIME
FROM
Inserted i
WHERE
DATEPART(HOUR, i.CHECKTIME) >= 12
As many SQL developers did, you had experienced the same error with SQL Server triggers. You have missed the case when multiple insert statements are executed on the table.
Triggers are executed only once for the SQL statement, not for each row.
So the code in the trigger should be able to cover all tasks for all rows affected by the SQL statement.
Here is a sample how you can alter your trigger code.
ALTER TRIGGER [dbo].[trgAfterInsert] ON [dbo].[CHECKINOUT]
AFTER INSERT
AS
INSERT INTO Att_process (USERID, checkin_time, checkout_time)
select
USERID,
case when DATEPART(HOUR, CHECKTIME) < 12 then CHECKTIME else null end,
case when DATEPART(HOUR, CHECKTIME) < 12 then null else CHECKTIME end
FROM inserted
For obtaining stable results, please use the Inserted and Deleted internal tables which are available within trigger codes.
You can further check sample SQL Trigger to log changes for how Deleted and Inserted tables are used.
I hope that helps you,
One last note, if you can use SQL Output clause to insert data into two tables at the same time.
But of course triggers work on table base, so where ever the insert statement is executed triggers work. If you use Output clause, you should guarantee that the only SQL statement which inserts data into that table will be it to maintain consistency between two tables.
Try this, since you are not using 'inserted' magic table to extract last inserted data.
DECLARE #uid INT;
DECLARE #checkin DATETIME;
SELECT #uid = USERID FROM inserted
SELECT #checkin = CHECKTIME FROM inserted
I want to copy a row from one table to other table before an update happens on specific column. this column named StartDateTime_ in my table.
can anyone correct this trigger if it is not correct or need optimisation.
CREATE TRIGGER PeriodicHistory_TR_U ON order_db..Periodic
FOR UPDATE
AS IF update(StartDateTime_)
begin
declare #Identity_;
declare #Version_;
DECLARE #Revision_;
declare #Identifier_;
declare #CreationTime_;
declare #CreationUserId_;
declare #StartDateTime_;
SELECT #Identity_= i.Identity_ from inserted i;
SELECT #Version_= i.Version_ from inserted i;
SELECT #Identifier_= i.Identifier_ from inserted i;
SELECT #CreationTime_= i.CreationTime_ from inserted i;
SELECT #CreationUserId_= i.CreationUserId_ from inserted i;
SELECT #StartDateTime_= i.StartDateTime_ from inserted i;
set #Revision_ = #Version_ +1;
insert into order_db..PeriodicHistory(Identity_,Version_,Revision_,Identifier_,CreationTime_,CreationUserId_,StartDateTime_)
values(#Identity_,#Version_,#Identifier_,#CreationTime_,#CreationUserId_,#StartDateTime_);
end
How about this:
CREATE TRIGGER PeriodicHistory_TR_U ON Periodic
FOR UPDATE
AS IF update(StartDateTime_)
BEGIN
INSERT INTO PeriodicHistory(Identity_,Version_,Revision_,Identifier_,CreationTime_,CreationUserId_,StartDateTime_)
SELECT d.Identity_,d.Version_,d.Revision_ + 1,d.Identifier_,d.CreationTime_,d.CreationUserId_,d.StartDateTime_
FROM deleted d
JOIN inserted i
ON d.Identity_ = i.Identity_
END
This eliminates the need for temporary variables.
Think about what happens if the update on Periodic affects more than one row. The original trigger won't be able to store multiple values...
Also, my revised trigger updates the version in the history table in the same way as your original trigger. You probably want to update the version in the Periodic table and keep the old value in the history. As it is after ten updates you could end up with: Periodic.Version_ = 1
PeriodicHistory.Version_ = 2
I have an SQL trigger as below
GO
create trigger ExpDateCheckCard
On Card
FOR Insert, Update
As
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 )
BEGIN
UPdate Card set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
END
If i am right as per the trigger for every record inserted/updated when trigger runs it will check for the YEAR In the ExpirationDate column of that record and if the value is less than 1971 then it will update it with the date in the update query.
The weird thing is it is not working as expected.
The if condition does not seem to work.
Is anything wrong with this particular trigger.
YES - there's definitely something fundamentally wrong with the way you wrote this trigger.
SQL Server (assuming that's what you're using) will fire the trigger not once per row (as many folks including yourself) seem to think - the trigger is fired once per batch which might update or insert 10, 20, 50 rows at once.
Therefore, the Inserted pseudo table inside the trigger can (and will!) contain multiple rows - and in that case - what exactly does your statement here select?
Select #expDate = inserted.ExpirationDate from inserted
Either you'll just get one randon row (out of 50) and handle that (and ignore all 49 other rows), or you'll get an error....
You need to write your triggers with that in mind - you MUST always assume that Inserted (and Deleted) will contain multiple rows!
So you need to change your trigger to be something like:
CREATE TRIGGER ExpDateCheckCard
ON dbo.Card
FOR Insert, Update
AS
UPDATE dbo.Card
SET ExpirationDate = '1900-01-01 00:00:00'
FROM dbo.Card c
INNER JOIN inserted i ON i.RecordID = c.RecordID
WHERE YEAR(i.ExpirationDate) < 1971
(I've also changed your old-style JOIN syntax (comma-separated list of tables) to the ANSI standard that's been in place since 1992 - please do not use the comma-separated list of tables! Use proper ANSI JOINs. See this blog post for more background info: Bad habits to kick : using old-style JOINs)
try with this :
create trigger ExpDateCheckCard
On Card
FOR Insert, Update
As
BEGIN
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 ) Begin
UPdate Card set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
end
END
You have to try this code
CREATE TRIGGER ExpDateCheckCard AFTER INSERT,Update ON Card
FOR EACH ROW
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 )
BEGIN
update Card
set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
END
I want a SQL trigger to fire when a table is updated. I have a table called SimActivation and a stored procedure called spUpdateMnpDate that takes two parameters, msisdn(varchar) and date(datetime).
When the SimActivation table is updated I want to call the procedure with the new values.
Here is what I got at the moment, after changing it back and forth:
USE StoreSale;
GO
CREATE TRIGGER dbo.tSyncMnpDate
ON [dbo].[SimActivation]
AFTER UPDATE
AS
DECLARE #date datetime
DECLARE #msisdn varchar(10)
SET #date = (select ProcessDate from inserted)
SET #msisdn = (select Msisdn from inserted)
EXEC [spUpdateMnpDate]
#msisdn, #date;
GO
Can anyone point me in the right direction?
Thanks :)
The problem you have is that a trigger will fire when one or more rows have been updated. At the moment you are assuming your trigger will fire for each row, which is not the case in SQL Server.
If the stored procedure you are trying to call is fairly simple I'd pull the code out of there and in to the trigger. But remember you are working with sets of changed rows (even if the change is to only one row) so you have to write your SQL accordingly.
EDIT: I assume your procedure is updating a date where the PK is equal to #msisdn, if so you can do this in your trigger:
UPDATE Your_Table
SET Your_Table.ProcessDate = inserted.ProcessDate
FROM Your_table INNER JOIN inerted ON Your_Table.Msisdn = inserted.Msisdn
Joining the tables ensures it will work for one or many updated rows.