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.
Related
Why not exists recompile option for trigger?
Suddenly the performance of one of our procedure (multiple SELECTs, multiple tables, insert into table) went from returning data in around 1 secs to 10-30secs.
After adding various debugging and logging we noticed that the performance would increase from the slow 10-30secs, back to sub-second speeds. (because alter trigger one of the table)
Just to clarify. The sequence of events:
Slow performance of Insert
Alter trigger table
Fast performance of Insert
I think slow performance associated with create wrong plan cash. because, before call insert command on the procedure, I write print datetime and the beginning of the the trigger command, add print datetime, so when call the procedure before alter trigger, The time difference between the first print and the second print is 20 sec, but when alter trigger, back to sub-second speeds. It should be noted that the commands in the trigger are not complicated
so, I need to add recompile option to trigger like procedure
it is trigger Script sample:
create trigger t_test on tbl AFTER insert
as
begin
begin try
declare #yearid int,
#id int
select #id = id,#yearid = yearid
from inserted
if exists(select * from FinancialYear where id = #yearid and flag = 0)
begin
raiserror('year not correct',16,1)
end
DECLARE #PublicNo BIGINT=(SELECT ISNULL(MAX(PublicNo),0)+1 FROM tbl)
update tbl
set PublicNo = #PublicNo
where #id
insert into tbl2
values (...)
end try
begin catch
print error_message()
end catch
end
I'm new to SQL Server and stored procedures and could do with a couple of pointers regarding transaction handling on a bug I've inherited.
I have two stored procedures, one inserts a record passed into it, then it calls another one where the first thing it does is read what was inserted.
But sometimes it completes successfully without processing the data. My suspicion is that the selects are happening before the insert has 'hit' the table and retrieve no records, and the stored procedure doesn't handle that.
I don't have time to re-engineer just yet, but the transaction handling looks suspect. Below is a rough outline of what the stored procedures do.
procedure sp1
(#id, #pbody)
as
begin
begin try
set nocount on;
begin
insert into tbl1 (id, tbody)
values (#id, #pbody)
exec sp2 #id
end
end try
begin catch
execute sperror
end catch
end
go
procedure sp2 (#id)
as
begin
begin try
set nocount on;
declare #vbody varchar(max)
select #vbody = tbody -- I don't believe this step always retrieves the row inserted by sp1
from tbl1 with (nolock)
where id = #id
create table #tmp1 (id, msg)
insert into #tmp1
select id, msg
from openjson........
while exists(select top 1 * from #tmp1) -- this looks similar to above, not sure the insert has finished before the read
begin
** do some stuff **
end
end try
begin catch
execute sperror
end catch
end
go
sp2 is using the WITH (NOLOCK) query hint, which can have unintended side-effects. Missing rows is just one of them.
Using NOLOCK? Here's How You'll Get the Wrong Query Results. - Brent Ozar UnlimitedĀ®
I'd strongly recommend removing that hint unless you really understand what it does and have a very good reason for using it.
I'm new to SQL Server error handling, and my English isn't too clear, so I apologize in advance for any misuderstandings.
The problem is: I insert multiple records into a table. The table has an AFTER INSERT trigger, which is processing the records one by one in the FETCH WHILE cycle with a cursor. If something error happens, everything is rolling back. So if there is just one wrong field in the inserted records, I lost all of them. And the insert rolls back also, so I can't find the wrong record. So I need to handle the errors inside of the cursor, to rollback only the wrong record.
I made a test database with 3 tables:
tA
VarSmallint smallint
VarTinyint tinyint
String varchar(20)
tB
ID int (PK, identity)
Timestamp datetime (default: getdate())
VarSmallint smallint
VarTinyint tinyint
String varchar(20)
tC
ID int PK
Timestamp datetime
VarTinyint1 tinyint
VarTinyint2 tinyint
String varchar(10)
tA contains 3 records with 1 wrong one. I insert this content into tB.
tB has the trigger, and inserts the records into tC ony by one.
tC has only tinyint variables, so there can be problem to insert values greater than 255. This is the point where the error occurs for the test!
My trigger is:
ALTER TRIGGER [dbo].[trg_tB]
ON [dbo].[tB]
AFTER INSERT
AS
BEGIN
IF ##rowcount = 0
RETURN;
SET NOCOUNT ON;
DECLARE
#ID AS int,
#Timestamp AS datetime,
#VarSmallint AS smallint,
#VarTinyint AS tinyint,
#String AS varchar(20),
DECLARE curNyers CURSOR DYNAMIC
FOR
SELECT
[ID], [Timestamp], [VarSmallint], [VarTinyint], [String]
FROM INSERTED
ORDER BY [ID]
OPEN curNyers
FETCH NEXT FROM curNyers INTO #ID, #Timestamp, #VarSmallint, #VarTinyint, #String
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
BEGIN TRAN
INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String])
VALUES (#ID, #Timestamp, #VarSmallint, #VarTinyint, #String)
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
INSERT INTO [dbo].[tErrorLog]([ErrorTime], [UserName], [ErrorNumber],
[ErrorSeverity], [ErrorState],
[ErrorProcedure], [ErrorLine],
[ErrorMessage], [RecordID])
VALUES (SYSDATETIME(), SUSER_NAME(), ERROR_NUMBER(),
ERROR_SEVERITY(), ERROR_STATE(),
ERROR_PROCEDURE(), ERROR_LINE(),
ERROR_MESSAGE(), #ID)
END CATCH
FETCH NEXT FROM curNyers INTO #ID, #Timestamp, #VarSmallint, #VarTinyint, #String
END
CLOSE curNyers
DEALLOCATE curNyers
END
If I insert 2 good records with 1 wrong, everything is rolling back and I got an error:
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
Please help me! How to modify this trigger to work well?
If I insert the wrong record, I need:
All the inserted records in tB
All the good records in tC
Error logged in tErrorLog
Thanks!
You have TWO major disasters in your trigger:
do not use a cursor inside a trigger - that's just horrible! Triggers fire whenever a given operation happens - you have little control over when and how many times they fire. Therefore, in order not to compromise your system performance too much, triggers should be very small, fast, nimble - do not do any heavy lifting and extensive processing in a trigger. A cursor is anything but nimble and fast - it's a resource-hog, processor-hog, memory-leaking monster - AVOID those whenever you can, and most definitely inside a trigger! (and you don't need them, 99% of the cases, anyway)
You can rewrite your whole logic into this one single, fast, set-based statement:
ALTER TRIGGER [dbo].[trg_tB]
ON [dbo].[tB]
AFTER INSERT
AS
BEGIN
INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String])
SELECT
[ID], [Timestamp], [VarSmallint], [VarTinyint], [String]
FROM
INSERTED
END
Never call COMMIT TRAN inside a trigger. The trigger executes inside the context and transaction of the statement that caused it to fire - if everything is OK, just let the trigger finish and then the transaction will be committed just fine. If you need to abort, call ROLLBACK. But never ever call COMMIT TRAN in the middle of a trigger. Just don't.....
I deleted the TRIGGER, and copy-pasted the code from it into a STORED PROCEDURE.
Then I added a row Status to tB, and set defaults to 0.
1 is "Record processed OK", 2 is "Record processing fault".
I fill the cursor with WHERE Status = 0.
In the TRY section I update the status to 1, in the CATCH section I UPDATE it to 2.
I have no jobs, so I run the SP from Windows scheduler with a batch file with the SQLCMD command.
Now the processing works well, moreover it worked well for the first time. Thanks for the help!
On our SQL Server (Version 10.0.1600), I have a stored procedure that I wrote.
It is not throwing any errors, and it is returning the correct values after making the insert in the database.
However, the last command spSendEventNotificationEmail (which sends out email notifications) is not being run.
I can run the spSendEventNotificationEmail script manually using the same data, and the notifications show up, so I know it works.
Is there something wrong with how I call it in my stored procedure?
[dbo].[spUpdateRequest](#packetID int, #statusID int output, #empID int, #mtf nVarChar(50)) AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #id int
SET #id=-1
-- Insert statements for procedure here
SELECT A.ID, PacketID, StatusID
INTO #act FROM Action A JOIN Request R ON (R.ID=A.RequestID)
WHERE (PacketID=#packetID) AND (StatusID=#statusID)
IF ((SELECT COUNT(ID) FROM #act)=0) BEGIN -- this statusID has not been entered. Continue
SELECT ID, MTF
INTO #req FROM Request
WHERE PacketID=#packetID
WHILE (0 < (SELECT COUNT(ID) FROM #req)) BEGIN
SELECT TOP 1 #id=ID FROM #req
INSERT INTO Action (RequestID, StatusID, EmpID, DateStamp)
VALUES (#id, #statusID, #empID, GETDATE())
IF ((#mtf IS NOT NULL) AND (0 < LEN(RTRIM(#mtf)))) BEGIN
UPDATE Request SET MTF=#mtf WHERE ID=#id
END
DELETE #req WHERE ID=#id
END
DROP TABLE #req
SELECT #id=##IDENTITY, #statusID=StatusID FROM Action
SELECT TOP 1 #statusID=ID FROM Status
WHERE (#statusID<ID) AND (-1 < Sequence)
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
END ELSE BEGIN
SET #statusID = -1
END
DROP TABLE #act
END
Idea of how the data tables are connected:
From your comments I get you do mainly C# development. A basic test is to make sure the sproc is called with the exact same arguments you expect
PRINT '#packetID: ' + #packetID
PRINT '#statusID: ' + #statusID
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
This way you 1. know that the exec statement is reached 2. the exact values
If this all works than I very good candidate is that you have permission to run the sproc and your (C#?) code that calls it doesn't. I would expect that an error is thrown tough.
A quick test to see if the EXEC is executed fine is to do an insert in a dummy table after it.
Update 1
I suggested to add PRINT statements but indeed as you say you cannot (easily) catch them from C#. What you could do is insert the 2 variables in a log table that you newly create. This way you know the exact values that flow from the C# execution.
As to the why it now works if you add permissions I can't give you a ready answer. SQL security is not transparent to me either. But its good to research yourself a but further. Do you have to add both guest and public?
It would also help to see what's going inside spSendEventNotificationEmail. Chances are good that sproc is using a resource where it didn't have permission before. This could be an object like a table or maybe another sproc. Security is heavily dependent on context/settings and not an easy problem to tackle with a Q/A site like SO.
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.