SQL Trigger not working as expected - sql

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

Related

SQL bulk copy : Trigger not getting fired

I am copying data from Table1 to Table2 table using sql bulk copy. I have applied trigger on Table2, but my trigger is not firing on every row. Here is my trigger and sqlbulkcopy function.
SqlConnection dstConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Destination"].ConnectionString);
string destination = dstConn.ConnectionString;
//Get data from Source in our case T1
DataTable dataTable = new Utility().GetTableData("Select * From [db_sfp_ems].[dbo].[tbl_current_data_new] where [start_date]>'" + calculate_daily_Time + "' and status=0" , source);
SqlBulkCopy bulkCopy = new SqlBulkCopy(source, SqlBulkCopyOptions.FireTriggers)
{
//Add table name of source
DestinationTableName = "tbl_current_data",
BatchSize = 100000,
BulkCopyTimeout = 360
};
bulkCopy.WriteToServer(dataTable);
//MessageBox.Show("Data Transfer Succesfull.");
dstConn.Close();
------Trigger-----
ALTER TRIGGER [dbo].[trgAfterInsert] ON [dbo].[tbl_current_data]
AFTER INSERT
AS
BEGIN
declare #intime datetime
declare #sdp_id numeric
declare #value numeric(9,2)
SELECT #intime= DATEADD(SECOND, -DATEPART(SECOND, start_date), start_date) FROM INSERTED
SELECT #sdp_id= sdp_id FROM INSERTED
SELECT #value= value FROM INSERTED
INSERT INTO Table3(sdp_id,value,start_date)
VALUES
(
#sdp_id,#value,#intime
)
A trigger is fired after an insert, whether that insert concerns 0, 1 or multiple records makes no difference to the trigger. So, even though you are inserting a whole bunch of records, the trigger is only fired once. This is by design, and not specific for BULK_INSERT; this is true for every kind of insert. This also means that the inserted pseudo table can hold 0, 1 or multiple records. This is a common pitfall. Be sure to write your trigger in such a way it can handle multiple records. For example: SELECT #sdp_id= sdp_id FROM INSERTED won't work as expected if inserted holds multiple records. The variable will be set, but you cannot know what value (from which inserted record) it's going to hold.
This is all part of the set based philosophy of SQL, it is best not to try and break that philosophy by using loops or other RBAR methods. Stay in the set mindset.
Your trigger is simply broken. In SQL Server, triggers handle multiple rows at one time. Assuming that inserted has one row is fatal error -- and I wish it caused a syntax error.
I think this is the code you want:
ALTER TRIGGER [dbo].[trgAfterInsert] ON [dbo].[tbl_current_data]
AFTER INSERT
AS
BEGIN
INSERT INTO Table3 (sdp_id, value, start_date)
SELECT sdp_id, value,
DATEADD(SECOND, -DATEPART(SECOND, start_date), start_date)
FROM inserted i;
END;
Apart from being correct, another advantage is that the code is simpler to write.
Note: You are setting the "seconds" part to 0. However -- depending on the type -- start_date could have fractional seconds that remain. If that is an issue, ask another question.

SQL Trigger update/Insert

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

Performance issues with UPDATE in AFTER UPDATE Trigger

I have a small performance issue with one of my database triggers in my MS-SQL Server 2014 database.
CREATE TRIGGER [dbo].[TRG_T_TPM_Vehicle_Update] ON [dbo].[T_TPM_Vehicle]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE T_TPM_Vehicle SET LastUpdated = getdate()
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted)
UPDATE T_TPM_Vehicle SET [DisturbedSince] = getdate()
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted WHERE inserted.Emergency_Stop = 1)
AND Vehicle_Number IN (SELECT Vehicle_Number FROM deleted WHERE deleted.Emergency_Stop = 0)
INSERT INTO T_TPM_Vehicle_HistoricalData
([Vehicle_Ref]
,[Vehicle_Number]
,[Vehicle_Type]
,[Pos_X]
,[Pos_Y]
,[Alpha]
,[LastAutoPos_X]
,[LastAutoPos_Y]
,[LastAutoAlpha]
,[Automatic]
,[Manual]
,[Blocked]
,[Loaded]
,[Stoped]
,[Emergency_Stop]
,[User_Required]
,[BatteryAlmostEmpty]
,[BatteryEmpty]
,[BatteryLevel]
,[ChargingRelaisEnable]
,[NavOK]
,[PowerOn]
,[Available]
,[OperatingMinutes]
,[UpdateOperatingMinutes]
,[DataChangedByVIS]
,[Blockingsreleased]
,[Cancelled]
,[ProductID]
,[HUIdent1]
,[HUIdent2]
,[HUType]
,[DisturbedSince])
SELECT inserted.[Vehicle_Ref]
,inserted.[Vehicle_Number]
,inserted.[Vehicle_Type]
,inserted.[Pos_X]
,inserted.[Pos_Y]
,inserted.[Alpha]
,inserted.[LastAutoPos_X]
,inserted.[LastAutoPos_Y]
,inserted.[LastAutoAlpha]
,inserted.[Automatic]
,inserted.[Manual]
,inserted.[Blocked]
,inserted.[Loaded]
,inserted.[Stoped]
,inserted.[Emergency_Stop]
,inserted.[User_Required]
,inserted.[BatteryAlmostEmpty]
,inserted.[BatteryEmpty]
,inserted.[BatteryLevel]
,inserted.[ChargingRelaisEnable]
,inserted.[NavOK]
,inserted.[PowerOn]
,inserted.[Available]
,inserted.[OperatingMinutes]
,inserted.[UpdateOperatingMinutes]
,inserted.[DataChangedByVIS]
,inserted.[Blockingsreleased]
,inserted.[Cancelled]
,inserted.[ProductID]
,inserted.[HUIdent1]
,inserted.[HUIdent2]
,inserted.[HUType]
,inserted.[DisturbedSince]
FROM inserted
END
What it basically does is it sets the LastUpdated column for all rows in inserted and the DisturbedSince column for a subset of the inserted rows.
Finally the inserted rows get copied to a history table. (Every change on any row must be saved for two days). Older data gets deleted by a maintenance job.
As we have up to ~ 300 rows updated per second (Updates to rows can be batched together) We create a big amount of data and recursive updates.
I've now found the INSTEAD OF UPDATE triggers which seem to solve the recursive UPDATE problem caused by my trigger but I would have to process every row of the inserted table one by one with an update statement in the trigger.
I'm not sure if this is really faster. Does anyone of you have a recommendation?
What I really need is to tweak / extend the data rows before they are send to the table. Is there an approach for this?
e.g.: Something like:
CREATE TRIGGER ... INSTEAD OF UPDATE
AS
BEGIN
UPDATE inserted SET LastUpdated = getdate()
UPDATE inserted SET DisturbedSince
WHERE Vehicle_Number IN (SELECT Vehicle_Number FROM inserted WHERE inserted.Emergency_Stop = 1)
AND Vehicle_Number IN (SELECT Vehicle_Number FROM deleted WHERE deleted.Emergency_Stop = 0)
"SAVE INSERTED"
END
and an AFTER UPDATE TRIGGER with the storage of the changed data to the history table.
Thank you for any suggestions.
Thomas
You're right to think that using an INSTEAD OF trigger is the right way to go rather than an AFTER trigger, when you're wanting to change data within the same table as well.
It would be something like:
CREATE TRIGGER [dbo].[TRG_T_TPM_Vehicle_Update] ON [dbo].[T_TPM_Vehicle]
INSTEAD OF UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE tgt
SET
Vehicle_Ref = i.Vehicle_Ref,
Vehicle_Type = i.Vehicle_Type,
...
LastUpdated = getdate(),
DisturbedSince = CASE WHEN i.Emergency_Stop=1 and d.Emergency_Stop=0
THEN getdate() ELSE d.DisturbedSince END
OUTPUT
inserted.[Vehicle_Ref]
,inserted.[Vehicle_Number]
,inserted.[Vehicle_Type]
...
,inserted.[HUIdent2]
,inserted.[HUType]
,inserted.[DisturbedSince]
INTO T_TPM_Vehicle_HistoricalData
([Vehicle_Ref]
,[Vehicle_Number]
,[Vehicle_Type]
...
,[HUIdent2]
,[HUType]
,[DisturbedSince])
FROM
T_TPM_Vehcile tgt
inner join
inserted i
on
tgt.Vehicle_Number = i.Vehicle_Number
inner join
deleted d
on
tgt.Vehicle_Number = d.Vehicle_Number
You'll note that I've combined both the UPDATEs and the INSERT into the history table into a single compound statement.
You'll also note that it's slightly confusing because there are two inserteds in play here - the inserted as part of the trigger (aliased as i to sidestep some of the confusion) and the inserted as part of the OUTPUT clause.

SQL Insert Trigger - Update other record

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;

SQl trigger after insert issue

I am working on triggers using SQL server 2005. I have a trigger for a table which is after update. After declaring the variables, the code is like this.
if #isconfirmed_before = 0 and #isconfirmed_after = 1
begin
if #invite_userid <> ''
begin
select #points = points from dbo.InvitePoint where code = 'USR' and packageid = #packageid
INSERT INTO InviteCount
([userID]
,[joinMerchantID]
,[packageID]
,[points]
,[joinDate])
VALUES
(#invite_userid
,#merchantid
,#packageid
,#points
,getdate())
end
SET #alpha_numeric=''
SELECT #alpha_numeric=#alpha_numeric+CHAR(n) FROM
(
SELECT TOP 8 number AS n FROM master..spt_values
WHERE TYPE='p' and (number between 48 and 57 or number between 65 and 90)
ORDER BY NEWID()
) AS t
update merchant
set reg_code = #alpha_numeric
where merchantid = #merchantid
END
The last part of
update merchant
set reg_code = #alpha_numeric
where merchantid = #merchantid
This reg_code shoule be inserted only once when the row is inserted but it is changing every time there is an update to the table. How do i make it happen. Please help me, Thank you in advance!!
update merchant
set reg_code = #alpha_numeric
where merchantid = #merchantid
That code is executing every time there is an update, because you have no conditional flow preventing it from executing sometimes. If you want to only execute that given a certain condition, you'll need to wrap it in an IF block.
You say: "This reg_code shoule be inserted only once when the row is inserted but it is changing every time there is an update to the table." Why is that in an AFTER UPDATE trigger then? It looks like it should be in an AFTER INSERT trigger. Unless I am misunderstanding you.
OK you are very much in trouble with triggers. First you need to show the actual create trigger code. I suspect you don't have it set correctly to only fire on insert.
Next you need to assume that the trigger must be able to handle multiple record inserts. Anytime you are setting a value to a scalar variable in a trigger, you are probably doing something wrong. This can't be your whole trigger either because you haven't shown how you get the varible values.