I have a merge statement supposed to execute a trigger multiple times.
I first thought my trigger wasn't executing, but with some research I found that triggers are only triggered once per statement (a trigger being one statement).
But all the posts out there are old and I thought that there might be a simple way now to make my trigger execute multiple times.
So is there anything I can add to my trigger or my merge statement to make my trigger do so?
Thanks
TRIGGER
TRIGGER [dbo].[Sofi_TERA_Trigger]
ON [dbo].[ZZ]
AFTER INSERT,UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF EXISTS(SELECT 1 FROM inserted WHERE inserted.Statut LIKE '%CLOT%' OR inserted.Statut LIKE '%CLTT%' OR inserted.Statut LIKE '%CONF%')
BEGIN
DECLARE #Id int;
DECLARE #Matricule varchar(10);
DECLARE #IdAction int;
DECLARE #NumeroOF int;
SELECT #NumeroOF = inserted.Ordre from inserted;
DECLARE OF_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT Id,Log.Matricule,IdAction from Log inner join (select max(Id) as maxID,Matricule from LOG where Log.NumeroOF = #NumeroOF group by Matricule) maxID
on maxID.maxID = Log.Id where Log.NumeroOF = #NumeroOF;
OPEN OF_CURSOR
FETCH NEXT FROM OF_CURSOR INTO #Id,#Matricule,#IdAction
WHILE ##FETCH_STATUS = 0
BEGIN
IF #IdAction!=13
BEGIN
IF #IdAction<=2
BEGIN
insert into Log(NumeroOF,Matricule,IdAction,Date,EstAdmin) values (#NumeroOF,#Matricule,13,GETDATE(),1);
END
ELSE
BEGIN
insert into Log(NumeroOF,Matricule,IdAction,Date,EstAdmin) values (#NumeroOF,#Matricule,2,GETDATE(),1);
insert into Log(NumeroOF,Matricule,IdAction,Date,EstAdmin) values (#NumeroOF,#Matricule,13,GETDATE(),1);
END
END
FETCH NEXT FROM OF_CURSOR INTO #Id,#Matricule,#IdAction
END
CLOSE OF_CURSOR;
DEALLOCATE OF_CURSOR;
END
END
MERGE STATEMENT
Merge ZZ AS TARGET USING ZZTemp AS SOURCE
ON (Target.Operation=Source.Operation AND Target.Ordre=Source.Ordre)
WHEN MATCHED THEN
UPDATE SET TARGET.DateTERA=SOURCE.DateTERA, TARGET.MatTERA=SOURCE.MatTERA, TARGET.MatTERC=SOURCE.MatTERC
WHEN NOT MATCHED THEN
INSERT(Operation,Ordre,ElementOTP,Article,DesignationOF,PosteTravail,ValeurTemps,DHT,Statut,StatutOF,TexteActivite,DateTERA,MatTERA,MatTERC,StatutMat)
VALUES(SOURCE.Operation,SOURCE.Ordre,SOURCE.ElementOTP,SOURCE.Article,SOURCE.DesignationOF,SOURCE.PosteTravail,SOURCE.ValeurTemps,SOURCE.DHT,
SOURCE.Statut,SOURCE.StatutOF,SOURCE.TexteActivite,SOURCE.DateTERA,SOURCE.MatTERA,SOURCE.MatTERC,SOURCE.StatutMat);
Your problem is the cursor is incorrectly written to handle sets of data. Any trigger setting a value form inserted or deleted to a scalar variable is incorrect and for reasons of data integrity MUST be rewritten. This trigger is buggy. Period. There is no getting around that it must be rewritten (and any others that use the same technique).
The code inside your trigger should be something like:
INSERT INTO Log(NumeroOF,Matricule,IdAction,Date,EstAdmin)
SELECT max(Id),l.Matricule,l.IdAction, 13,GETDATE(),1
FROM Log l
JOIN Inserted i ON l.NumeroOF = i.Ordre
WHERE i.Statut LIKE '%CLOT%' OR i.Statut LIKE '%CLTT%' OR i.Statut LIKE '%CONF%'
GROUP BY l.Matricule,l.IdAction
INSERT INTO Log(NumeroOF,Matricule,IdAction,Date,EstAdmin)
SELECT max(Id),l.Matricule,l.IdAction, 2,GETDATE(),1
FROM Log l
JOIN Inserted i ON l.NumeroOF = i.Ordre
WHERE IdAction<=2
WHERE i.Statut LIKE '%CLOT%' OR i.Statut LIKE '%CLTT%' OR i.Statut LIKE '%CONF%'
GROUP BY l.Matricule,l.IdAction
Make sure to test with both single record and multiple record inserts as all triggers should be tested. Then try your MERGE once you are confident the trigger is correct.
Related
I have a problem with trigger which I use for procedure implementation. I use inserted table to collect all rows which user updated and procure procedure. But procedure repeat for ech row separately. How to handle this?
my trigger:
ALTER TRIGGER [dbo].[FormUpdate]
ON [dbo].[FORM]
For UPDATE
AS
BEGIN
SET NOCOUNT ON;
select * into #inserted from (
SELECT i.* from FORM gw
inner join inserted i on gw.FORMID = i.FORMID) t
WHERE t.PREPARING <> 0
IF (SELECT COUNT(*) FROM #inserted) > 0
BEGIN
UPDATE GW
SET PREPARING = 0
FROM FORM GW
INNER JOIN #inserted on GW.FORMID = #inserted.FORMID
EXEC dbo.PREPARING_OF_THE_FORM
END
END
I may be way off but i think i am pretty close.
As it looks right now the Form table is updated and then the stored procedure runs. I am guessing the stored procedure runs some work on the form table. If you want to run it on every row it looks like (i hate to say it) you need a CURSOR.
You will need to create a cursor from your first select something like
select * into #inserted from (
SELECT i.* from FORM gw
inner join inserted i on gw.FORMID = i.FORMID) t
WHERE t.PREPARING <> 0
DECLARE CURSOR inserted_Cursor
FOR
SELECT *
FROM #inserted
OPEN inserted_cursor
FETCH NEXT FROM inserted_cursor
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE GW
SET PREPARING = 0
FROM FORM GW
INNER JOIN #inserted on GW.FORMID = #inserted.FORMID
EXEC dbo.PREPARING_OF_THE_FORM
FETCH NEXT FROM inserted_cursor
END
CLOSE inserted_cursor
DEALLOCATE inserted_Cursor
I haven't had a chance to test this so it may take some fiddling to get it to run. I am not sure what the stored procedure is doing but you will need to make sure it can handle one record at a time. It may be best to not use the stored procedure and just write out the code to ensure it can handle the one row at a time.
Also keep in mind there are better ways to do this but with your specific application and without knowing the architecture this is what i think the best solution for you.
For a homework assignment, I'm trying to build a trigger that allows for multiple inserts/updates/deletes by utilizing a cursor. We have to use a cursor in order to practice the syntax. We know that there are very few practical scenarios for cursors in a production environment.
Here's what I'm trying to accomplish:
For each row inserted into the TAL_ORDER_LINE table, update the ON_HAND value in the TAL_ITEM table by subtracting the NUM_ORDERED value from the stored ON_HAND value.
Table Structure:
Current Query:
ALTER TRIGGER update_on_hand
ON TAL_ORDER_LINE
AFTER INSERT AS
DECLARE #vItemNum as char
DECLARE #vNumOrdered as int
DECLARE new_order CURSOR FOR
SELECT ITEM_NUM, NUM_ORDERED
FROM inserted
OPEN new_order;
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
WHILE ##FETCH_STATUS=0
BEGIN
UPDATE TAL_ITEM
SET ON_HAND = ON_HAND - #vNumOrdered
WHERE ITEM_NUM = #vItemNum
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
END
CLOSE new_order
DEALLOCATE new_order
My Insert Query:
INSERT INTO TAL_ORDER_LINE (ORDER_NUM, ITEM_NUM, NUM_ORDERED, QUOTED_PRICE)
VALUES (51626, 'KL78', 10, 10.95), (51626, 'DR67', 10, 29.95)
It runs successfully, but does not affect the ON_HAND value. I think the biggest problem is that I'm struggling to understand cursor syntax, especially the INTO clause in the FETCH statement and how data from the 'inserted' table is passed into the cursor. What do I need to know to get this to work? Thanks in advance!
Your problem is likely due to this:
DECLARE #vItemNum as char
it is HIGHLY unlikely that the ItemNum column is a single character. For future reference, you should always verify that you variable definitions are consistent with the values you expect to store in them. And as has been hinted - you will get better answers by posting a complete script rather than a picture.
Big question,how you gonna debug ?
Is On_Hand col NULL , then do this isnull(on_Hand,0)
DECLARE #vItemNum as char
DECLARE #vNumOrdered as int
DECLARE new_order CURSOR FOR
SELECT ITEM_NUM, NUM_ORDERED
FROM TAL_ORDER_LINE
OPEN new_order;
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
WHILE ##FETCH_STATUS=0
BEGIN
--UPDATE TAL_ITEM
--SET ON_HAND = ON_HAND - #vNumOrdered
--WHERE ITEM_NUM = #vItemNum
print #vItemNum
print vNumOrdered
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
END
CLOSE new_order
DEALLOCATE new_order
Try this :
ALTER TRIGGER update_on_hand ON TAL_ORDER_LINE
FOR INSERT AS
BEGIN
UPDATE TI
SET TI.ON_HAND = TI.ON_HAND - I.NUM_ORDERED
TAL_ITEM TI INNER JOIN
INSERTED I ON I.ITEM_NUM = TI.ITEM_NUM
END
Changed Trigger to FOR INSERT Trigger
Removed Cursor
Note: NOT Tested. ( If you post the sql scripts for create table + sample inserts I can give it a try )
I'm using the following query.
select * from OPENQUERY(EXITWEB,N'SET NOCOUNT ON;
declare #result table (id int);
insert into [system_files] ([is_public], [file_name], [file_size], [content_type], [disk_name], [updated_at], [created_at])
output inserted.id into #result(id)
values (N''1'',N''7349.jpg'',N''146921'',N''image/jpeg'',N''5799dcc8a1eb1413195192.jpg'',N''2016-07-28 10:22:00.000'',N''2016-07-28 10:22:00.000'')
declare #id int = (select top 1 id from #result)
select * from system_files where id = #id
insert into linkToExternal (id, id_ext) values(#id, 47)
--select #id
')
when I perform a select from within the query it works just fine:
But when I go to check my database when the call has finished, the record is no longer there.
So I'm suspecting a transaction is rolled back. My question is: why. What can I do to prevent the transaction to be rolled back if that's the case.
Well, as always, after days of struggling and me post a question on stackoverflow I find the solution: http://www.sqlservercentral.com/Forums/Topic1128997-391-1.aspx#bm1288825
I was having the same problem as you and almost gave up on it but have
finally found an answer to the problem. Reading an article about
sharing data between stored procedures I discovered that OPENQUERY
issues an Implicit Transaction and that it was Rolling back my insert.
So I had to add an explicit Commit to my stored procedures, in
additional I discovered that if I use it in a query that has a Union
it has to be Commited twice. Since I'm doing my insert inside a BEGIN
TRY I can always just commit twice and not worry about whether it is
being used in a UNION. I'm returning different values if there is an
error but that was just apart of my debugging.
SELECT TOP 5 *
FROM mm
JOIN OPENQUERY([LOCALSERVER], 'EXEC cms60.dbo.sp_RecordReportLastRun ''LPS'', ''Test''') RptStats ON 1=1
ALTER PROCEDURE [dbo].[sp_RecordReportLastRun]
-- Add the parameters for the stored procedure here
#LibraryName varchar(50),
#ReportName varchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
BEGIN TRY
INSERT INTO cms60.dbo.ReportStatistics (LibraryName, ReportName, RunDate) VALUES (#LibraryName, #ReportName, GETDATE())
--
COMMIT; --Needed because OPENQUERY starts an Implicit Transaction but doesn't commit it.
COMMIT; --Need second Commit when used in a UNION and although it throws an error when not used in a UNION doesn't cause a problem.
END TRY
BEGIN CATCH
SELECT 2 Test
END CATCH
SELECT 1 Test
END
In my case, adding a ;COMMIT; after the inserts solved it, and made sure it got written into the database.
basically i am looking for logic to generate insert or update statement in string variable from trigger. suppose when people just update 'N' of fields like update statement....then my trigger will fire and from that trigger i want build what update statement was issued by user and store in string variable
the same way i want to build insert statement from trigger too but i am not getting logic. so if anyone has any idea or sample code then please share with me to achieve my goal.
ALTER TRIGGER WSContent AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
DECLARE #Action VARCHAR(10)
DECLARE #PKValue INT
DECLARE #TableName VARCHAR(50)
SET #TableName='website_content'
IF EXISTS(SELECT * FROM INSERTED)
BEGIN
IF EXISTS(SELECT * FROM DELETED)
BEGIN
SET #Action ='U';
SELECT #PKValue=ContentNumber from DELETED
END
ELSE
BEGIN
SET #Action ='I';
SELECT #PKValue=ContentNumber from INSERTED
END
END
ELSE
BEGIN
SET #Action = 'D';
SELECT #PKValue=ContentNumber from DELETED
END;
INSERT INTO [ContentChangeLog]([PkValue],[TableName],[Action],ActionDate)
VALUES(#PKValue,#TableName,#Action,GetDate())
SET NOCOUNT OFF;
Like many people, you misunderstand how a trigger works. When you insert, update or delete multiple records, the trigger is called once and the tables deleted/inserted can contain multiple records, not 1 for each record effected. You need to rewrite this assuming you have multiple records in those tables.
INSERT INTO [ContentChangeLog]([PkValue],[TableName],[Action],ActionDate)
SELECT ContentNumber, #TableName, 'I', GETDATE()
FROM INSERTED i
WHERE not exists(SELECT TOP 1 1 FROM DELETED WHERE ContentNumber = i.ContentNumber)
Here is an example of the Inserted records only, you will want to do something similar for your updates and deletes.
For the application I work on... we're creating a custom logging system. The user can view logs and apply "Tags" to them (Just like how you can apply tags to questions here!)
In this example, I'm trying to get a list of all the Logs given a "Tag." I realize I can accomplish this by using joins... but this is also an exercise for me to learn Stored Procedures a little better :)
I have a stored procedure that looks something like this to select a log by the PK
ALTER PROCEDURE [dbo].[getLogByLogId]
-- Add the parameters for the stored procedure here
#ID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1
LOG_ID,
a.A,
a.B,
a.C
FROM dbo.LOG a
WHERE a.LOG_ID = #ID
Now I would like to call this Stored Procedure from another... something like this
ALTER PROCEDURE [dbo].[getLogsByTagName]
-- Add the parameters for the stored procedure here
#TAG nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1000
LOG_ID --somehow store this and execute the dbo.getLogByLogId procedure here
FROM dbo.LOG_TAG a
WHERE a.TAG = #TAG
Thanks
If you have complex logic in your logbyid SP which you are trying to avoid reproducing in multiple places in your system (choice of columns, derived columns, etc), I would recommend turning that into an inline table-valued function instead (potentially without taking the ID parameter, in which case, you can actually use an ordinary view).
Then you can either join to that ITVF/view in your other stored proc (or also make another udf) which does the search or use the OUTER APPLY functionality (not as efficient).
Inline table-valued functions are basically parameterized views and can be optimized fairly easily by the optimizer.
If you want to call another sproc from within a sproc just use:
CREATE PROCEDURE myTestProc
AS
BEGIN
--Do some work in this procedure
SELECT blah FROM foo
--now call another sproc
EXEC nameOfSecondSproc
END
The only way you can achive what you are attempting is by using a CURSOR.
If this is for your learning only, then by all means, give this a go, but I would not recomend this for production.
It would go something like this
DECLARE #Table TABLE(
ID INT
)
INSERT INTO #Table SELECT 1
INSERT INTO #Table SELECT 2
INSERT INTO #Table SELECT 3
INSERT INTO #Table SELECT 4
INSERT INTO #Table SELECT 5
INSERT INTO #Table SELECT 6
DECLARE Cur CURSOR FOR
SELECT ID
FROM #Table
OPEN Cur
DECLARE #ID INT
FETCH NEXT FROM Cur INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #ID
FETCH NEXT FROM Cur INTO #ID
END
CLOSE Cur
DEALLOCATE Cur
By using the #ID retrieved in the WHILE loop, you can then execute the sp you wish and insert the values into a table variable.
INSERT INTO #Table EXEC sp_MySP #ID
You can call a stored procedure from another using the following syntax:
ALTER PROCEDURE [dbo].[getLogsByTagName]
-- Add the parameters for the stored procedure here
#TAG nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1000
LOG_ID --somehow store this and execute the dbo.getLogByLogId procedure here
FROM dbo.LOG_TAG a
WHERE a.TAG = #TAG
-- Execute dbo.getLogByLogId stored procedure
DECLARE #logId INTEGER
SET #logId = <some value>
EXEC dbo.getLogByLogId #logId
END
However, the difficult part of your question is that your dbo.getLogByLogId procedure can only accept a single LogID parameter and therefore will only be able to return a single Log record. You need to return information for all Logs where the LogId has a corresponding record in the Tags table.
The correct way to do this would be to JOIN the Log and Tag tables together, like so:
SELECT *
FROM dbo.LOG_TAG a
INNER JOIN dbo.LOG b ON a.LOG_ID = b.LOG_ID
WHERE a.TAG = #TAG
If you are concerned about returning the same logId multiple times, you can use the DISTINCT keyword in the SELECT statement to filter out the duplicated logIds.
You may also be able to rewrite your dbo.getLogByLogId procedure as a user-defined function (UDF). UDFs can accept a table as a parameter and return a table result.
An introduction to user-defined functions can be found in this article.