Can not get ##ERROR after EXEC() with Error - sql

Please see the following t-sql code
DECLARE #iError INT
EXEC('select * from sysobj')
SELECT #iError = ##ERROR
PRINT 'Error = ' + CAST(#iError AS VARCHAR(10))
After I run it, it returns error message, which is what I want.
Msg 208, Level 16, State 1, Line 1
Invalid object name 'sysobj'.
Error = 208
However, if I change the query
DECLARE #iError INT
EXEC('select * from sysobjects where ''c'' = 1')
SELECT #iError = ##ERROR
PRINT 'Error = ' + CAST(#iError AS VARCHAR(10))
The output will be
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'c' to data type int.
The problem is any code afer EXEC() is not executed.
In the real stored procedure, I have some code to handle the errors after PRINT. And none of those code executed in second case.
Could someone let me know why this happens?
I tested it on both SQL Server 2008 and 2005.
Thanks

Different errors will have different levels of abort and some of these can be controlled through the use of settings, like ARITHIGNORE and ANSI_WARNINGS.
I suggest reading the following article that explains all of this in great detail.
http://www.sommarskog.se/error-handling-I.html#whathappens
Statement-termination and Batch-abortion
These two groups comprise regular run-time errors, such as duplicates in unique
indexes, running out of disk space etc. As I have already have discussed, which
error that causes which action is not always easy to predict beforehand. This
table lists some common errors, and whether they abort the current
statement or the entire batch.

Look into implementing TRY...CATCH for your error handling. You should be able to put all of your error handling in the CATCH block then.

There's an article on this on msdn here. It has to do with the context in which the EXEC() statement is run. If you include the error trap in the parameter passed to EXEC(), you can stuff it into a temp table and then look at that.
Here's their example:
DECLARE #cmd VARCHAR(1000), #ExecError INT
CREATE TABLE #ErrFile (ExecError INT)
SET #cmd = 'EXEC GetTableCount ' +
'''pubs.dbo.authors''' +
'INSERT #ErrFile VALUES(##ERROR)'
EXEC(#cmd)
SET #ExecError = (SELECT * FROM #ErrFile)
SELECT #ExecError AS '##ERROR'

Related

How can I insert text, from a subquery, in a function request

I'm using a function on the Master database (xp_fileexist) to check if a folder is empty or not. If it's empty I want a 0, otherwise a 1.
If I hardcode the folder name ('C:\Import\2016-01-01\Transaction') it works fine. What doesn't work for me, is having the date as a variable, as the date changes from time to time. For the variable, I use this:
'C:\Import\The Netherlands\'+CAST((select workingdate from system..tmpworkingdate) AS VARCHAR(10))+'\Transaction(BP)'
This is the code I've tried:
CREATE TABLE #temp (FileExists int, IsDirectory int, ParentDirExists int)
INSERT INTO #temp
EXEC master..xp_fileexist ('C:\Import\'+CAST((select workingdate from system..tmpworkingdate) AS VARCHAR(10))+'\Transaction(BP)')
IF EXISTS(SELECT IsDirectory FROM #temp WHERE IsDirectory=1)
PRINT 1
ELSE
PRINT 0
DROP TABLE #temp
Error: Msg 102, Level 15, State 1, Line 5 Incorrect syntax near
'C:\Import\The Netherlands\'. Msg 156, Level 15, State 1, Line 5
Incorrect syntax near the keyword 'AS'.
Does anyone have a clue?
EXEC doesn't allow string manipulations (or any expression evaluation). Define the value beforehand:
DECLARE #filename VARCHAR(MAX);
SET #filename = 'C:\Import\'+CAST((select workingdate from system..tmpworkingdate) AS VARCHAR(10))+'\Transaction(BP)';
EXEC master..xp_fileexist (#filename);
That said, you should use CONVERT() or FORMAT() to be sure you get the format you really want. You wouldn't want system changes to totally break this code.
EDIT:
Argg! I didn't realize that EXEC master..xp_fileexist doesn't even allow string variables on the execution line. So, you have to do the whole thing as dynamic SQL:
DECLARE #sql NVARCHAR(MAX) = 'EXEC master..xp_fileexist ''' + #filename + '''';
EXEC(#sql);
(There are examples on the web that do use variables, so maybe this depends on the SQL Server version.)

Syntax error in sql exec count

declare #message int,#DBName varchar(50)
set #DBName ='AutoChip'
exec('select '+#message+'= count(*) from '+#DBname+'.[dbo].[Report List]')
print #message
Getting an error trying to print the count
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '='.
I will pass DBname dynamically, I am using a cursor
The variable name needs to be part of dynamic SQL you are creating, but it's shouldn't be concated with it.
For example, if your variable is of type varchar and has value as 'ERROR', resultant query will be
select ERROR= count(*) from YOURDBNAME.[dbo].[Report List]
so the correct one is below.
exec('select #message= count(*) from '+#DBname+'.[dbo].[Report List]')
You need to include variable declaration and initialization in dynamic sql. You cannot set a value of external variable inside a dynamic sql as the context of execution will be different.

SQL IF block code causing error even though it shouldn't execute

I have a case in which I have SQL code with an IF-ELSE block. The code in the IF portion should not be reached, but I still get an error when the SQL executes. In the code I first test for the linked server, and when that fails, #reval is set to 1 and the ELSE block should execute and avoid the code in the IF block that needs to query the linked server, but I get this error:
Msg -1, Level 16, State 1, Line 0
SQL Server Network Interfaces: Error Locating Server/Instance Specified [xFFFFFFFF].
I am running the query from within SSMS 2012.
Why does this error occur?
declare #clientCode VARCHAR(7)
set #clientCode = '8001299'
declare #year INT
set #year = 2013
DECLARE #retVal INT
-- test connectivity with linked database server
BEGIN TRY
EXEC #retVal = sys.sp_testlinkedserver N'ATLAS'
END TRY
BEGIN CATCH
SET #retval = SIGN(##ERROR)
END CATCH
IF #retval = 0 -- connection attempt successful
BEGIN
--THE FOLLOWING INSERT SQL STATEMENT CAUSES THE ERROR
SET #contIndex = (SELECT ContIndex FROM ATLAS.Engine_sp.dbo.tblEngagement WHERE ClientCode = #clientCode)
END
ELSE -- could not connect
BEGIN
-- execute code to pull from linked server
END
It'll still parse and bind everything before it executes it. It's failing to bind here.
You could use sp_executesql to execute that line, and it should only validate when sp_executesql is actually called.

What leads to this strange SQL behavior?

Running SQL 2005 X64.
First, create the following stored proc on a database:
CREATE PROCEDURE dbo.Test
#Value int = null
AS
BEGIN
IF (IsNull(#Value, '') = '')
SELECT '*I am NULL!*'
ELSE
SELECT 'I am ' + CONVERT(varchar(20), #Value)
END
Try executing the above proc as follows, and you get the result below:
EXEC dbo.Test
I am NULL!
Now, ALTER the proc so that the EXEC statement is part of the sproc itself:
ALTER PROCEDURE dbo.Test
#Value int = null
AS
BEGIN
IF (IsNull(#Value, '') = '')
SELECT 'I am NULL!'
ELSE
SELECT 'I am ' + CONVERT(varchar(20), #Value)
END
EXEC dbo.Test
If you execute it now, you get...
I am NULL!
I am NULL!
I am NULL!
...ad infinitum until the output breaks with this error:
Msg 217, Level 16, State 1, Procedure
Test, Line 16 Maximum stored
procedure, function, trigger, or view
nesting level exceeded (limit 32).
Ignoring for the moment that this isn't at all a standard practice and that most likely someone would do something like this only by accident, could someone please provide some low-level insight on what SQL 2005 is "thinking" when the second incarnation of this proc is executed?
Your code is behaving as expected. The procedure is calling itself recursively.
If you do not want that, try this:
ALTER PROCEDURE dbo.Test
#Value int = null
AS
BEGIN
IF (IsNull(#Value, '') = '')
SELECT 'I am NULL!'
ELSE
SELECT 'I am ' + CONVERT(varchar(20), #Value)
END
GO
EXEC dbo.Test
If you do want to use recursion, you have to define a base case (AKA "exit condition") which will make stored procedure exit the recursion stack.
The recursion is because everything is being considered part of the proc, not just the BEGIN to END block.
From my comment:
No great mystery. It's going to treat everything until the next GO or other indicator of the end of the batch as part of the proc. The outermost BEGIN and END are not required syntax as part of the procedure.
It's called recursion, as others have mentioned.
You can avoid it as #Adrian has shown (using 'GO' to prevent the sp from calling itself), or you can also escape it using a control structure...
Here's a sample / experiment you can study if you want to learn about recursion: http://msdn.microsoft.com/en-us/library/aa175801.aspx
It allows for 32 nested calls. and with every Exec call you are nesting it forever. So think recursively.
Exec proc
Select
Exec
Select
exec
Infinitely.
once it reaches the 32nd nested calls it hits its maximum and says whoa i can not continue.
My reading of the question is not "Why is my SP exhibiting recursion?" but "Why is recursion limited to 32 and how do i get around that?"
I had completely forgotten that SQL Recursion dies on you like that.
An answer I just worked out is to make use of TRY-CATCH and ##NestLevel. The below is a small demonstrator rig. In your code it would be far better to have an independent end condition, for example running out of chunks to process.
My code has been mangled by the editor, I have no time to work round your issues.
BEGIN TRY DROP PROCEDURE dbo.Nester END TRY BEGIN CATCH END catch
GO
CREATE PROCEDURE dbo.Nester #NestLevel INT = 0 OUT
AS
BEGIN
DECLARE #MaxActNestLevel INT = 40;
SELECT #NestLevel += 1;
PRINT (CONVERT(sysname, ##NestLevel) + ' ' + CONVERT(sysname, #NestLevel))
IF #NestLevel < #MaxActNestLevel
BEGIN TRY
EXEC dbo.Nester #NestLevel OUT
END TRY
BEGIN CATCH
PRINT 'Catch Block'
PRINT (ERROR_NUMBER())
SELECT #NestLevel += 1;
IF ##NestLevel < 30 --AND ERROR_NUMBER() = 217
BEGIN
EXEC dbo.Nester #NestLevel OUT
END
ELSE
THROW
END CATCH
END
GO
EXEC dbo.Nester;

How can I get the actual stored procedure line number from an error message?

When I use SQL Server and there's an error, the error message gives a line number that has no correlation to the line numbers in the stored procedure. I assume that the difference is due to white space and comments, but is it really?
How can I relate these two sets of line numbers to each other? If anyone could give me at least a pointer in the right direction, I'd really appreciate it.
I'm using SQL server 2005.
IIRC, it starts counting lines from the start of the batch that created that proc. That means either the start of the script, or else the last "GO" statement before the create/alter proc statement.
An easier way to see that is to pull the actual text that SQL Server used when creating the object. Switch your output to text mode (CTRL-T with the default key mappings) and run
sp_helptext proc_name
Copy paste the results into a script window to get syntax highlighting etc, and use the goto line function (CTRL-G I think) to go to the error line reported.
Out of habit I place LINENO 0 directly after BEGIN in my stored procedures. This resets the line number - to zero, in this case. Then just add the line number reported by the error message to the line number in SSMS where you wrote LINENO 0 and bingo - you have the error's line number as represented in the query window.
If you use a Catch Block and used a RAISERROR() for any code validation within the Try Block then the Error Line gets reported where the Catch Block is and not where the real error occurred. I used it like this to clear that up.
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE() + ' occurred at Line_Number: ' + CAST(ERROR_LINE() AS VARCHAR(50)),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
Actually this Error_number() works very well.
This function starts counts from the last GO (Batch Separator) statement, so if you have not used any Go spaces and it is still showing a wrong line number - then add 7 to it, as in stored procedure in line number 7 the batch separator is used automatically. So if you use
select Cast(Error_Number()+7 as Int) as [Error_Number] - you will get the desired answer.
In TSQL / Stored Procedures
You may get an error such as:
Msg 206, Level 16, State 2, Procedure myproc, Line 177 [Batch Start Line 7]
This means that the error is on line 177 in the batch. Not 177 in the SQL. You should see what line number your batch starts on, in my case [7], and then you add that value to the line number to find what statement is wrong
you can use this
CAST(ERROR_LINE() AS VARCHAR(50))
and if you want to make error log table you can use this :
INSERT INTO dbo.tbname( Source, Message) VALUES ( ERROR_PROCEDURE(), '[ ERROR_SEVERITY : ' + CAST(ERROR_SEVERITY() AS VARCHAR(50)) + ' ] ' + '[ ERROR_STATE : ' + CAST(ERROR_STATE() AS VARCHAR(50)) + ' ] ' + '[ ERROR_PROCEDURE : ' + CAST(ERROR_PROCEDURE() AS VARCHAR(50)) + ' ] ' + '[ ERROR_NUMBER : ' + CAST(ERROR_NUMBER() AS VARCHAR(50)) + ' ] ' + '[ ERROR_LINE : ' + CAST(ERROR_LINE() AS VARCHAR(50)) + ' ] ' + ERROR_MESSAGE())
Helpful article on this issue:
http://tomaslind.net/2013/10/15/line-numbers-in-t-sql-error-messages/
"If you instead generate the script with Management Studio, the USE dbname statements and the settings for ANSI_NULLS and QUOTED_IDENTIFIER are added automatically. Remove these statements (9 rows) to get the line numbers correct in the script window:"
The long answer: the line number is counted from the CREATE PROCEDURE statement, plus any blank lines or comment lines you may have had above it when you actually ran the CREATE statement, but not counting any lines before a GO statement…
I found it much easier to make a stored proc to play around with to confirm:
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE ErrorTesting
-- Add the parameters for the stored procedure here
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 1/0
END
GO
After you’ve created it, you can switch it to ALTER PROCEDURE and add some blank lines above the comments and above and below the first GO statement to see the effect.
One very strange thing I noticed was that I had to run EXEC ErrorTesting in a new query window instead of highlighting it at the bottom of the same window and running… When I did that the line numbers kept going up! Not sure why that happened..
you can get error message and error line in catch block like this:
'Ms Sql Server Error: - ' + ERROR_MESSAGE() + ' - Error occured at: ' + CONVERT(VARCHAR(20), ERROR_LINE())
Just add the following code to your Stored procedure, to indicate the absolute line start number WITH "LINENO xx", where "xx" is the actual line number when you open the SP in SQL Mgt Studio
For example,
USE [Northwind]
GO
/****** Object: StoredProcedure [automate].[workorders_exceptions_generate] Script Date: 03/03/2021 8:49:23 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
Here are some comments here
Which are white spaces
But the actual line after the "BEGIN" statement is 21
*/
CREATE PROCEDURE dbo.something
#ChildWOID varchar(30)
, #DontByPass bit = 0
, #BillingStatus varchar(30) = null OUTPUT
AS
BEGIN
LINENO 21
PRINT 'HELLO WORLD'
END