OK, I am not that experienced with SQL 2005 error handling and am
learning my way around try/catch statements.
I have written the below procedure but no matter what I pass to it,
there is never any data in my ErrorLog table. I have passed all INT
values, all datetime values, or data strings that are not in the DB
and get '0 rows effected' with nothing reported in ErrorLog. It is
as if the CATCH statement is never reached (for what it is worth, I
have also tried commenting out the validation at the top).
Any ideas what I am doing wrong? Thanks.
ALTER PROCEDURE [dbo].[aspnet_Membership_UpdateLastActivityDate]
#UserId nvarchar(256),
#UserName nvarchar(256),
#LastActivityDate datetime,
#ApplicationName nvarchar(256)
AS
DECLARE #Today DATETIME
DECLARE #MSG VARCHAR(255)
DECLARE #Severity INT
DECLARE #ErrorCode INT
BEGIN
SET XACT_ABORT ON -- (I have also tried it without XACT_ABORT. No difference)
BEGIN TRY
SET #ErrorCode = 0
SELECT #Today = GetDate()
IF (#UserId IS NULL)
RETURN(1)
IF (#UserName IS NULL)
RETURN(1)
IF (#LastActivityDate IS NULL)
RETURN(1)
BEGIN TRAN
UPDATE dbo.aspnet_Users WITH (ROWLOCK)
SET LastActivityDate = #LastActivityDate
FROM dbo.aspnet_Users u
INNER JOIN dbo.aspnet_Applications a
ON u.ApplicationId = a.ApplicationId
WHERE u.UserName = #UserName
AND u.UserId = #UserId
AND a.ApplicationName = #ApplicationName
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
SET #ErrorCode = Error_Number()
SET #Severity = Error_Severity()
SET #MSG = 'An error was thrown: '
+ 'Error(' + #ErrorCode + '):' + ERROR_MESSAGE()
+ ' Severity = ' + ERROR_SEVERITY()
+ ' State = ' + ERROR_STATE()
+ ' Procedure = ' + ERROR_PROCEDURE()
+ ' Line Number = ' + ERROR_LINE()
INSERT INTO [dbo].[ErrorLog]([errornum], [errortype], [errormsg],[errorsource], [errordate])
VALUES (#ErrorCode, 'E', #MSG, Error_Procedure(), #Today)
RAISERROR(#MSG, #Severity, 2)
END CATCH
END
RETURN #ErrorCode
It has been awhile since I've done a lot with SQL Error handling but I don't see any place that is likely to generate an error. Are you expecting the "Return" statements to be "Caught"? That isn't going to happen...they'll just return from the function. You'll need to raise an error, not trigger a Return.
Agreed with #Mark. Try changing this:
IF (#UserId IS NULL)
RETURN(1)
To this:
IF (#UserId IS NULL)
BEGIN
RAISERROR('No UserID was passed in.', 11, 1);
RETURN 1;
END
Also see this article for a fantastic error handling primer by Erland Sommarskog:
http://www.sommarskog.se/error_handling_2005.html
Related
I'm trying to write a script that will exit as an error when there are currently SQL agent jobs running. I would also like it to succeed (exit 0) if there are no jobs running.
Something like below.
IF
EXEC msdb.dbo.sp_help_job #execution_status=1
THEN
RAISE ERROR
ELSE
COMPILE SUCCESS
The following code will help you. You may need to change the code for your required action.
First, you need to create a Stored Procedure:
CREATE PROCEDURE YourProc
AS
BEGIN
DECLARE #ErrMsg NVARCHAR(MAX), #ErrSeverity INT
SET XACT_ABORT OFF
BEGIN TRY
IF EXISTS (
SELECT *
FROM msdb.dbo.sysjobs_view job
INNER JOIN msdb.dbo.sysjobactivity activity
ON job.job_id = activity.job_id
WHERE activity.run_Requested_date IS NOT NULL
AND activity.stop_execution_date IS NULL)
BEGIN
SELECT #ErrMsg = 'There is a Job already running',
#ErrSeverity = 1;
RAISERROR (#ErrMsg, #ErrSeverity, 1);
END
ELSE
PRINT 'No Jobs running'
END TRY
BEGIN CATCH
SELECT #ErrMsg = ERROR_MESSAGE(),
#ErrSeverity = ERROR_SEVERITY();
RAISERROR (#ErrMsg, #ErrSeverity, 1);
END CATCH
END
Then, you can call your procedure:
EXEC YourProc
I have a trigger on a table that executes a stored procedure. The executed stored procedure has a TRY/CATCH, so that if there is an error, a row is inserted into a log table.
When the stored procedure fails, I'm getting the following error:
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
How do I make it so the update get committed and the CATCH in the stored procedure is also executed? If I add:
IF ##TRANCOUNT > 0
ROLLBACK TRAN
to the stored procedure, then I get the following error:
The transaction ended in the trigger. The batch has been aborted.
Trigger:
ALTER TRIGGER [dbo].[trigger123] ON [dbo].[tbl321]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE (status)
BEGIN
IF EXISTS (--some condition)
BEGIN
EXEC SProc
END
END
END
SProc:
ALTER PROCEDURE [dbo].[SProc ]
AS
BEGIN
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
BEGIN TRY
select #sql = '
declare #error1 varchar(255),
#error2 varchar(255),
#error3 varchar(255)
Exec SomeDB.DBO.ConfirmStatus ''A10594'',#error1 output,#error2 output,#error3 output
if ISNULL(#error1,0) <> 0
begin
set #error1 = ISNULL(#error2,'''') + '' '' + ISNULL(#error3,'''') + '' '' + ISNULL(#error1,0)
RAISERROR (#error1, 16, 1)
end'
from jobs j
exec(#sql) at [linked_server]
update status
set status_prev = 1
END TRY
BEGIN CATCH
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE()
INSERT INTO error_log (error_datetime, [error_message])
SELECT
GETDATE(),
'Msg: ' + ISNULL(CONVERT(VARCHAR, ERROR_NUMBER()), 'N/A') + ', Level: ' + ISNULL(CONVERT(VARCHAR, #ErrorSeverity), 'N/A') + ', Line: ' + ISNULL(CONVERT(VARCHAR, ERROR_LINE()), 'N/A') + ', Error: ' + ISNULL(#ErrorMessage, 'N/A')
END CATCH
END
I was able to modify my stored procedure to not use a try/catch. This gets me what I need.
ALTER PROCEDURE [dbo].[SProc ]
AS
BEGIN
DECLARE #error table (error1 varchar(255))
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
BEGIN TRY
select #sql = '
declare #error1 varchar(255),
#error2 varchar(255),
#error3 varchar(255)
Exec SomeDB.DBO.ConfirmStatus ''A10594'',#error1 output,#error2 output,#error3 output
if ISNULL(#error1,0) <> 0
begin
set #error1 = ISNULL(#error2,'''') + '' '' + ISNULL(#error3,'''') + '' '' + ISNULL(#error1,0)
select #error1
end'
from jobs j
insert into #error
exec(#sql) at [linked_server]
if exists (select top 1 error1 from #error)
begin
INSERT INTO error_log (error_datetime, [error_message])
SELECT
GETDATE(),
(select top 1 error1 from #error)
else
update status
set status_prev = 1
END
Having a table like this:
CREATE TABLE tmpDelete
(
ID INT IDENTITY(1, 1),
Value DATE
)
GO
Then I declare my procedure like this:
CREATE PROCEDURE [TestSP]
#prmDate DATE
AS
BEGIN
--PRINT '##TRANCOUNT : ' + CAST(##TRANCOUNT AS NVARCHAR(10))
BEGIN TRANSACTION TestTransaction
BEGIN TRY
--PRINT '##TRANCOUNT : ' + CAST(##TRANCOUNT AS NVARCHAR(10))
INSERT INTO dbo.tmpDelete
( Value )
VALUES ( 'abc' -- Value - date
);
COMMIT TRANSACTION TestTransaction
--PRINT '##TRANCOUNT : ' + CAST(##TRANCOUNT AS NVARCHAR(10))
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION TestTransaction
DECLARE #NVERRO_MESS NVARCHAR(4000);
DECLARE #INERR_SEVE INT, #INER_STAT INT;
SELECT #NVERRO_MESS = ERROR_MESSAGE(), #INERR_SEVE = ERROR_SEVERITY(), #INER_STAT = ERROR_STATE();
RAISERROR (#NVERRO_MESS, #INERR_SEVE, #INER_STAT)
--PRINT '##TRANCOUNT : ' + CAST(##TRANCOUNT AS NVARCHAR(10))
RETURN
END CATCH
END
GO
And when I call it from Entity Framework it returns the following error message:
Cannot roll back TestTransaction. No transaction or savepoint of that
name was found. Transaction count after EXECUTE indicates that a
COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count =
%1, current count = %2.
How should I declare my transactions?
I am getting a date value from a tblinfo table. If the date is null, then I will insert into table, else I will update the row in the table.
I am facing the issue that 2 sessions accessing the table simultaneously with same information, both are getting Null, so both are trying to insert records into same table with same primary key value. We are getting the primary key constraints issue. How to stop this?
begin tran
set #date=(select date from tblinfo where id=#id)
if(#date is null)
--insert
else
--update
commit tran
Using Locking
declare #Date DateTime
, #RowId bigint = 10
Begin Tran
select #Date = SomeDate
from tblInfo
with (RowLock) --this is the important point; during the transaction, this row will be locked, so other statements won't be able to read it
where Id = #RowId
if (#Date is Null)
begin
insert SomeTable (TblInfoId, Date)
values (#RowId, GetUtcDate())
end
else
begin
update SomeTable
set Date = #Date
where tblInfoId = #RowId
end
if ##TranCount > 0 Commit Tran
Technique Avoiding Locking
If preventing reads from occurring is an issue for some reason, the below is a lockless approach; but requires an extra field in your database.
declare #Date DateTime
, #RowId bigint = 10
, #DataAvailable bit
Begin Tran
update tblInfo
set session_lock = ##SPID
where Id = #RowId
and session_lock is null
select #Date = SomeDate
, #DataAvailable = 1
from tblInfo
where Id = #RowId
and session_lock = ##SPID
if (#DataAvailable = 1)
begin
if (#Date is Null)
begin
insert SomeTable (TblInfoId, Date)
values (#RowId, GetUtcDate())
end
else
begin
update SomeTable
set Date = #Date
where tblInfoId = #RowId
end
update tblInfo
set session_lock = null
where Id = #RowId
and session_lock = ##SPID
end
--optionally do something if the row was already "locked"
--else
--begin
--...
--end
if ##TranCount > 0 Commit Tran
Right after "begin tran", update your table like below script
begin tran
-- this update will lock the table so no other process can read data
update tblInfo with (tablock)
set date = date
---- do what ever you need to do here
set #date=(select * from tblinfo)
if(#date is null)
--insert
else
--update
commit tran
this will cause SQL Server lock the table and the second transaction will wait reading the data until the first process finish.
Relying on lock hints to get the desired outcome will perhaps prevent you from attempting to insert the value twice, but it won't prevent errors from happening. You'll just get an 'Unable to acquire lock' or deadlock error instead.
You should put a mutex in your code, in the minimally sized critical section. Using sp_getApplock, you can get a lock on a section of code with a wait period that you specify (subsequent threads will wait for the lock to clear and then continue). Here's some sample code:
declare #LockResource nvarchar(100) = 'VALUE_INSERT'
begin transaction
-- Fetch the lock:
EXEC #res = sp_getapplock
#Resource = #LockResource,
#LockMode = 'Exclusive',
#LockOwner = 'Transaction',
#LockTimeout = 10000,
#DbPrincipal = 'public'
if #res not in (0, 1)
begin
set #msg = 'Unable to acquire Lock for ' + #LockResource
raiserror (#msg , 16, 1)
rollback transaction
return
end
else
begin
begin try
---------------------------------------------
-- Fetch value if it exists, insert if not.
-- Both need to happen here.
---------------------------------------------
end try
begin catch
select #msg = 'An error occurred: ' + ERROR_MESSAGE()
-- Release the lock.
EXEC #res = sp_releaseapplock
#Resource = #LockResource,
#DbPrincipal = 'public',
#LockOwner = 'Transaction'
rollback transaction
raiserror(#msg, 11, 1)
goto cleanup
end catch
-- Release the lock.
EXEC #res = sp_releaseapplock
#Resource = #LockResource,
#DbPrincipal = 'public',
#LockOwner = 'Transaction'
end
commit transaction
I have this stored procedure, which works if I execute manually. But sometimes I see timeout exception on running this Stored Procedure. Is this is due to MERGE, http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
ALTER PROCEDURE [dbo].[InsertOrUpdateMobileUser]
(
#ID BIGINT
,#Name NVARCHAR(255)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION InsertOrUpdateMobileUser;
MERGE INTO MobileUsers MU
USING (SELECT #ID AS ID) T ON (MU.ID = T.ID)
WHEN MATCHED THEN
UPDATE SET [Name] = CASE WHEN #Name IS NULL OR #Name = '' THEN [Name] ELSE #Name END
WHEN NOT MATCHED THEN
INSERT ([Name])
VALUES (#Name)
SELECT *
FROM MobileUsers
WHERE ID = #ID;
LBEXIT:
IF #TranCount = 0
COMMIT;
END TRY
BEGIN CATCH
DECLARE #Error INT, #Message VARCHAR(4000), #XState INT;
SELECT #Error = ERROR_NUMBER() ,#Message = ERROR_MESSAGE() ,#XState = XACT_STATE();
IF #XState = -1
ROLLBACK;
IF #XState = 1 AND #TranCount = 0
rollback
IF #XState = 1 AND #TranCount > 0
ROLLBACK TRANSACTION InsertOrUpdateMobileUser;
RAISERROR ('InsertOrUpdateMobileUser: %d: %s', 16, 1, #error, #message) ;
END CATCH
END
You differentiate between two execution methods. What are they, exactly? You mean that it works if run only the code of the procedure, and doesn't work when you EXECUTE the proc? Of it works through EXECUTE, and fails in a job?
Timeouts only concern client applications. You have SqlCommand .CommandTimeout property in .NET, and in Management Studio there's Tools>Options>Query Execution>Command Timeout. If you have a job, then it should run infinitely, there's even no option to set the timeout in Sql Server Agent.