Stored Procedure delete statement after return - sql

So I'm tracking down a potential bug in a sync process I'm in charge of (written by someone else). When viewing one of the stored procedures that is being called, I noticed something peculiar. Based on my understanding of returns, anything after the return will not be returned. However, I am not positive if this is the case in SQL. Based on the chunk of SQL below, will the delete statement ever run? Or does the SP return information to signify whether rows were deleted (such as how many rows, whether it was successful, etc.)? I am assuming this is a bug in the SP, but want to confirm before taking action. Thanks in advance.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[DeleteSalesforce_Contacts]
AS
Return
Delete From dbo.Contacts Where IsDeleted = 1
GO

The documentation is pretty clear on this:
"Exits unconditionally from a query or procedure. RETURN is immediate
and complete and can be used at any point to exit from a procedure,
batch, or statement block. Statements that follow RETURN are not
executed."
The delete statement won't be executed.
The return statement takes an optional parameter, but to use a query as value you would need to use a select in parentheses. Example:
return (select top 1 id from SomeTable)

The delete would never happen when the proc is executed.
The only time a statement after the return is ever executed when a proc is run is if it was related to a goto process and the code was sent there and bypassed the return. This kind of code sometimes used to be written before Try Catch blocks were allowed in SQL Server to do something with errors.

Related

Avoiding concurrency of Stored Procedures

I have following scenraio:
I need to fire updating procedures in fixed time intervals. But some of the procedure might take longer than the interval. I want to avoid stacking calls to procedures (in case one want to start before its previous execution hasn't been finished).
My colleague advised me to create in database additional table with two columns:
one with name of procedures and IsActive column (bit). So, before executing any procedure, I check the corresponding value of IsActive. If it's 1, then abort execution.
Now, the problem:
when I get to execution, I need to set the value of IsActive to 1 for the procedure, which I try to do like this:
UPDATE ProcActivity SET IsActive = 1 WHERE ProcedureName = 'proc_name'
EXEC proc_name
UPDATE ProcActivity SET IsActive = 0 WHERE ProcedureName = 'proc_name'
But, SQL is executing batches, so the value of 1 isn't visible (the UPDATE isn't commited) until the procedure is finished.
So, how to commit this UPDATE? I tried with COMMIT, but didn't work... I can't use GO, because it's wrapped in IF statement...
Don't use transactions this way because of visibility, unless you want to use extra hints. WHich I would not do personally.
If you want "only one stored proc execution current" then I would consider sp_getapplock and sp_releaseapplock in the stored procedure.
This will enforce force "single threaded" execution.
Other questions here that show how to use it
And, to abort other calls, set the #LockTimeout=0, so if the result code is different from zero, then you know that you need to abort current call.

Will a stored procedure fail if one of the queries inside it fails?

Let's say I have a stored procedure with a SELECT, INSERT and UPDATE statement.
Nothing is inside a transaction block. There are no Try/Catch blocks either.
I also have XACT_ABORT set to OFF.
If the INSERT fails, is there a possibility for the UPDATE to still happen?
The reason the INSERT failed is because I passed in a null value to a column which didn't allow that. I only have access to the exception the program threw which called the stored procedure, and it doesn't have any severity levels in it as far as I can see.
Potentially. It depends on the severity level of the fail.
User code errors are normally 16.
Anything over 20 is an automatic fail.
Duplicate key blocking insert would be 14 i.e. non-fatal.
Inserting a NULL into a column which does not support it - this is counted as a user code error (16) - and consequently will not cause the batch to halt. The UPDATE will go ahead.
The other major factor would be if the batch has a configuration of XACT_ABORT to ON. This will cause any failure to abort the whole batch.
Here's some further reading:
list-of-errors-and-severity-level-in-sql-server-with-catalog-view-sysmessages
exceptionerror-handling-in-sql-server
And for the XACT_ABORT
https://www.red-gate.com/simple-talk/sql/t-sql-programming/defensive-error-handling/
https://learn.microsoft.com/en-us/sql/t-sql/statements/set-xact-abort-transact-sql
In order to understand the outcome of any of the steps in the stored procedure, someone with appropriate permissions (e.g. an admin) will need to edit the stored proc and capture the error message. This will give feedback as to the progress of the stored proc. An unstructured error (i.e. not in try/catch) code of 0 indicates success, otherwise it will contain the error code (which I think will be 515 for NULL insertion). This is non-ideal as mentioned in the comments, as it still won't cause the batch to halt, but it will warn you that there was an issue.
The most simple example:
DECLARE #errnum AS int;
-- Run the insert code
SET #errnum = ##ERROR;
PRINT 'Error code: ' + CAST(#errornum AS VARCHAR);
Error handling can be a complicated issue; it requires significant understanding of the database structure and expected incoming data.
Options can include using an intermediate step (as mentioned by HLGEM), amending the INSERT to include ISNULL / COALESCE statements to purge nulls, checking the data on the client side to remove troublesome issues etc. If you know the number of rows you are expecting to insert, the stored proc can return SET #Rows=##ROWCOUNT in the same way as SET #errnum = ##ERROR.
If you have no authority over the stored proc and no ability to persuade the admin to amend it ... there's not a great deal you can do.
If you have access to run your own queries directly against the database (instead of only through stored proc or views) then you might be able to infer the outcome by running your own query against the original data, performing the stored proc update, then re-running your query and looking for changes. If you have permission, you could also try querying the transaction log (fn_dblog) or the error log (sp_readerrorlog).

C# SQL transaction

I am using a C# class that is calling a SQL stored procedure in a serializable transaction.
So if something goes wrong in the stored procedure, everything is rolled back.
I have one statement in the SQL stored procedure that should be always executed (even if the stored procedure fails at some point and a rollback occurs). The statement is an update of a record.
I cannot change the C# library, so I need to do this in my stored procedure.
Is there some way I can execute that one statement outside the transaction?
You could perhaps use SAVE TRANSACTION. It is not supported in distributed transactions, and your statement must be executed first, so it might not be what you are looking for.
I have found the solution.
I didn't realize that SQL knew it was called in a transactional matter by the c# class.
The update statement that should always be executed is the first step (and also last step) in the procedure. Let me clarify:
I have an IF function. If it is true, the update should occur no matter what. If it is false, some transactional logic should be executed.
Now in the c# class, it expects a result from the stored proc. If the proc doesn't return a result (like it does in the update statement), it rollbacks the transaction.
So by just adding the following lines right after the update statement, the update occurs :)
IF ##TRANCOUNT > 0
BEGIN
COMMIT TRANSACTION
END
Your solution does not sound like a good one.. For example- if your stored procedure will be part of bigger transaction, then it will commit all changes made before it. Also I believe no one would guess that your proc has such behaviour without first seeing code.
The need to always execute some part of proc sounds like need for security audit. So maybe you should use trace, extended events or sql server audit instead.
if you really need what you say you need- you can use method described here: How to create an autonomous transaction in SQL Server 2008

Update Statement not updating when run from a stored procedure, but works perfectly when run manually

My first post here, so please be gentle with me :)
I have a stored procedure with which I take some source data, do some manipulation, run some update statements on it and then put the data into our main data table (I guess you could say its an ETL). The problem I have is some of the update statements I've written don't seem to have worked when the procedure has run, however, if I run them manually in a seperate query window they work perfectly.There are technically two parts to the update statement and one part updates and the other fails, which adds further complication to my trouble.
The snippet of code for the update is as follows:
UPDATE Prod_DDb.dbo.DataLoadTeleconnect
SET pCommissionValue = (SELECT Commission
FROM dbo.MappingiPhoneCommission
WHERE Prod_DDb.dbo.DataLoadTeleconnect.pMRC BETWEEN BaseMRC AND HighMRC),
pMRCBand = (SELECT MRCBand
FROM dbo.MappingiPhoneCommission
WHERE Prod_DDb.dbo.DataLoadTeleconnect.pMRC BETWEEN BaseMRC AND HighMRC)
WHERE pMapID = 'iPhone'
The code updates 2 columns in my source table where the MRC of the record falls between the base and high mrc. Commission is the value which is not updating, however MRCBand updates correctly.
The MappingiPhoneCommission table has the following columns:
BaseMRC
HighMRC
Commission
MRCBand
If anyone could shed any light onto why this would fail in the stored procedure but run fine in a new query window I would be most appreciative.
If you require any further information please let me know and i will try to supply what is needed.
Kind Regards
Tony
I see no reason for this code to act differently in a stored procedure, so I would assume the stored procedure makes a change in the data before the update. I would advise adding some checks to your stored procedure to see what the actual data at runtime is. You might start adding the following just before the update:
SELECT * FROM Prod_DDb.dbo.DataLoadTeleconnect WHERE pMapID = 'iPhone'
SELECT Commission FROM dbo.MappingiPhoneCommission, Prod_DDb.dbo.DataLoadTeleconnect
WHERE Prod_DDb.dbo.DataLoadTeleconnect.pMRC BETWEEN dbo.MappingiPhoneCommission.BaseMRC AND dbo.MappingiPhoneCommission.HighMRC
SELECT MRCBand FROM dbo.MappingiPhoneCommission, Prod_DDb.dbo.DataLoadTeleconnect
WHERE Prod_DDb.dbo.DataLoadTeleconnect.pMRC BETWEEN dbo.MappingiPhoneCommission.BaseMRC AND dbo.MappingiPhoneCommission.HighMRC
If the stored proc does not have a try catch block, then put one in. Likely the SP is failing just above this update which is why it doesn;t happen, without a trycatch block, you are probably not rolling back the whole transaction or bubbling up the actual error.
you can use FOR loop calling the columns in select statement and then updating the columns. this will help you out.

Stored Procedure Return Value to Fail SQL Job

I have a stored procedure which is the first step in an SQL Job. The SP compares dates in 2 tables, if they equal then the SQL job can continue, but if they are not I need to return value from the SP that causes the SQL Job to trigger it's on failure action.
What is the best way to do this? Is it with an RAISERROR statement or just return a value like -99?
Seems such an obvious question, but I've never thought about it before until now.
Whichever way you want. You can use a try/catch and raiserror. That can allow you to write the error to the event log as well if desired.
Typically, I'd just use an IF statement that does a return and then proceed with the rest of the code otherwise.
I think it's a matter of preference/requirements, though. Either will get the job done.
In my opinion the best practice approaches are:
a function that returns a bit 1/0 (True/False)
stored proc output parameter
Proc return values are meant to be used to test the success or failure of the proc itself, usually 0 for success and some other error number when there is an error