Using a cursor, how to handle the error in SQL - sql-server

I have multiple databases in which I am looking for a particular column called Countries. If the column exists then I check the space characters in the column. If I loop through a cursor, the DB which is not having a Countries column will throw an error. How can I handle this error?
Concern: the catch block is not handling, please help me how to resolve the issue.
Query as shown below,
CREATE PROCEDURE [dbo].[USP_SMSGeneric_CountrySpace] #DB VARCHAR(100)
As
BEGIN
SET NOCOUNT ON
DECLARE #StudyID varchar(max)
DECLARE #Databasename VARCHAR(max)
DECLARE #QUERY NVARCHAR(MAX)
DECLARE #Protocol varchar(max)
DECLARE #Servername varchar(max)
DECLARE #script VARCHAR(Max)
DECLARE #script1 VARCHAR(Max)
DECLARE #initscript NVARCHAR(Max)
DECLARE #Countries VARCHAR(Max)
DECLARE #Countryrelease VARCHAR(Max)
IF OBJECT_ID('TEMPDB..#OBJMISSING') IS NOT NULL DROP TABLE #OBJMISSING
CREATE TABLE #OBJMISSING (ERRID INT IDENTITY(1,1),ERRNUM BIGINT,ERRMSG VARCHAR(MAX),DBNAME VARCHAR(MAX))
SET #initscript='
DECLARE csrStudy CURSOR FOR
SELECT ProtocolName, DBName, studyid,DBServer AS Servername from SMSAPP.dbo.studymaster WITH (NOLOCK)
WHERE ClientName LIKE ''%NOVARTIS%'' AND studystatus IN (1,2) AND DBServer IN (''SQL002'' ,''SQL004'',''SQL005'')
'
EXEC sp_executesql #initscript
OPEN csrStudy
FETCH NEXT FROM csrStudy INTO #Protocol,#Databasename,#StudyID,#ServerName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #DB = #Servername+'.'+#Databasename
SET #script = '
DECLARE #StrValue VARCHAR(max)
BEGIN TRY
IF EXISTS (
SELECT DISTINCT 1 FROM '+#DB+'.sys.columns c JOIN '+#DB+'.sys.Tables t ON c.Object_ID=t.Object_ID
WHERE c.Name = ''Countries’'' AND t.name =''tblMaterials'')
BEGIN
SELECT #StrValue = ISNULL(#StrValue + '','', '''') + Countries’ FROM (
SELECT DISTINCT (LEN(Countries’ + '','') - LEN(REPLACE(Countries’, '' '', '''') + '',''))CNT,Countries
FROM '+#DB+'.dbo.tblMaterials WITH (NOLOCK) )A WHERE CNT>0
END
END TRY
BEGIN CATCH
INSERT INTO #OBJMISSING VALUES
(ERROR_NUMBER(),ERROR_MESSAGE(),''+#Databasename+'')
END CATCH
IF #StrValue IS NOT NULL -- If any Duplicate values found, then raise an alert
BEGIN
SELECT '+#StudyID+' As StudyID,
''Countries field value Should not have space'' AS Actual ,
''Countries field value exists with space for String :'' + #StrValue AS Discrepancy INTO #tempOutput
I'm getting the following error:
The OLE DB provider "SQLNCLI10" for linked server "SQL001" does not
contain the table ""RAW."dbo"."tblMaterials"". The table either does
not exist or the current user does not have permissions on that table

You need a TRY CATCH outside the dynamic SQL.
The error message that is displayed is at parse time, before it even executes (for that EXEC statement). At this moment the engine validates that the tables and objects exist and if not then an error is returned. The execution never starts so it will never get to the CATCH section. This is why the TRY CATCH needs to be outside the dynamic SQL, because the whole dynamic SQL will get rejected after parsing.
The error message you are getting is coming from a query like the following:
EXEC('SELECT * FROM [SomeLinkedServer].DatabaseName.SchemaName.NonExistingTable')
Msg 7314, Level 16, State 1, Line 1 The OLE DB provider "SQLNCLI11"
for linked server "SomeLinkedServer" does not contain the table
""DatabaseName"."SchemaName"."NonExistingTable"". The table either does not exist or
the current user does not have permissions on that table.
If you can wrap this on a TRY CATCH, then the control flow will jump to the catch since the severity of the error is high enough:
BEGIN TRY
EXEC('SELECT * FROM [SomeLinkedServer].DatabaseName.SchemaName.NonExistingTable')
END TRY
BEGIN CATCH
SELECT 'This is the catch section'
END CATCH
Please note the difference against this following example, without dynamic SQL:
BEGIN TRY
SELECT 1 FROM [SomeLinkedServer].DatabaseName.SchemaName.NonExistingTable
END TRY
BEGIN CATCH
SELECT 1
END CATCH
Msg 208, Level 16, State 1, Line 3 Invalid object name
'DatabaseName.SchemaName.NonExistingTable'.
This is because the whole batch is being rejected after parsing, so it can't jump to a CATCH as it never started execution. When you use dynamic SQL, the parse, compile and execute of the dynamic portion happens at the EXEC point (and that is exactly why it's dynamic), delaying the error throw so it can be caught.
I can't supply the full fixed code because what you posted isn't complete. But you should be able to ignore errors if you follow this guideline:
DECLARE #Variable ...
DECLARE MyCursor CURSOR FOR ...
FETCH NEXT FROM MyCursor INTO #Variable
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
DECLARE #DynamicSQL VARCHAR(MAX) = ... -- The DynamicSQL may have another TRY CATCH inside
EXEC(#DynamicSQL)
END TRY
BEGIN CATCH
-- Do your catch operation here, you can leave this section empty if you want (not recommended)
END CATCH
FETCH NEXT FROM MyCursor INTO #Variable
END

Related

Table doesn't exists by table name

Is it possible in SQL Server to check whether table exists in this way? If it doesn't exists, it will run catch
declare #SQL varchar(4444)
select #SQL = '
begin try
select * from ServerName.DBName.dbo.TableNAme
end try
begin catch
select 1
end catch'
exec (#SQL)
I don't want to use solution described here , because I want to use exact same structure of table as above.
Reason: I will run several dynamic queries in loop, and above ServerName, DbName ,TableName gonna be passed as a parameter.
It should work if you do it dynamically. When you don't do it dynamically, the missing table will be caught at parse time, and the CATCH will not be triggered.
EDIT: I mean like this:
declare #SQL varchar(4444)
select #SQL = 'select * from ServerName.DBName.dbo.TableNAme'
begin try
exec (#SQL)
end try
begin catch
select 1
end catch

Dynamic SQL parameter error, Incorrect syntax near '#myparametername'

I'm building a fun stored procedure that will use dynamic SQL, sp_executesql with parameters, to allow some alter statements for a column in all database tables if the column name exists ( As you can see I used a cursor for loop all the tables on DB)
I built a test but the parameter doesn't work, I get the next error on each alter table statement that runs
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '#parTablename'.
The next is the code
SET NOCOUNT ON;
GO
DECLARE #tablename varchar(100);
DECLARE #alteredColumn varchar(100)='[mycolumn] [datetimeoffset](0) NOT NULL;';
DECLARE #column varchar(100)='mycolumn';
DECLARE #parDefinition nvarchar(500) = N'#parTablename nvarchar(100)';
DECLARE #sqlCommand nvarchar(1000)= N'ALTER TABLE #parTablename ALTER COLUMN '+#alteredColumn;
DECLARE ALTERCURSOR CURSOR LOCAL FAST_FORWARD FOR
SELECT name AS tablename
FROM sys.Tables
OPEN ALTERCURSOR;
FETCH NEXT FROM ALTERCURSOR INTO #tablename
WHILE ##FETCH_STATUS = 0
BEGIN
--print #tablename
IF EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tablename AND COLUMN_NAME = #column)
BEGIN
EXECUTE sp_executesql #sqlCommand, #parDefinition,#parTablename = #tablename
END
FETCH NEXT FROM ALTERCURSOR INTO #tablename
END
CLOSE ALTERCURSOR;
DEALLOCATE ALTERCURSOR;
SET NOCOUNT OFF;
GO
SOLUTION
Apparently is not possible to send a table name as a parameter, instead of that I used the #SeanLange option for degub with a little modification
SET #sqlCommand =Replace(#sqlCommand, '#parTablename',QUOTENAME(#tablename))
EXECUTE sp_executesql #sqlCommand
You can't stick a parameter in the middle of your dynamic sql like this. You need to use PRINT instead of EXECUTE to debug this. I wouldn't use a cursor for this myself but if you go that path you will have to do something like this before the EXECUTE statement.
Set #sqlCommand = Replace(sqlCommand, '#parTablename', #parTablename)

error in multi batch transactional sql script

BEGIN TRAN
SET XACT_ABORT ON
GO
BEGIN TRY
IF OBJECT_ID('dbo.Offer_GetByStudyId', 'p') IS NULL
EXEC ('CREATE PROCEDURE Offer_GetByStudyId as select 1')
END TRY
BEGIN CATCH
THROW;
END CATCH
GO
IF ##error <> 0 and ##trancount > 0 ROLLBACK
IF ##trancount = 0 BEGIN SET NOCOUNT ON; SET NOEXEC ON; END
GO
BEGIN TRY
ALTER PROCEDURE dbo.Offer_GetByStudyId
#StudyId NVARCHAR(MAX) = NULL
AS
BEGIN
DECLARE #Conditions NVARCHAR(MAX) = '';
IF #StudyId IS NOT NULL
BEGIN
SET #Conditions = #Conditions + ' AND o.StudyId = ' + cast(#StudyId as varchar(10))
END
DECLARE #sql NVARCHAR(MAX) = 'SELECT
o.StudyId as StudyId,
o.SampleId as SampleId,
o.Status as Status,
o.Title as Title,
o.Topic as Topic,
o.Description as Description,
o.TestOffer as TestOffer,
T.CPI as CPI
FROM Offers o
LEFT JOIN [dbo].[Terms] T ON (o.[Id] = T.[OfferId]) AND T.Active = 1
WHERE 1 = 1' + #Conditions
EXEC(#sql)
END
END TRY
BEGIN CATCH
THROW;
END CATCH
This is my SQL script, I'm trying to have a multi batch script run as a single transaction, so if one statement fails, all of it will be rolled back. But I here keep getting this error:
Incorrect syntax near BEGIN. expecting EXTERNAL
The begin they are talking about is the one after:
#StudyId NVARCHAR(MAX) = NULL
You need to remove your "GO" statements. These are not part of the TSQL language, they are just statements to tell SSMS/SQLCMD that the batch above should be executed. I'm not clear on the behavior of a transaction split across several GO statements. I would start my removing the "GO"s.
https://msdn.microsoft.com/en-us/library/ms188037.aspx
If you take out the statement ALTER PROCEDURE dbo.Offer_GetByStudyId (Line 22 - 47) from TRY CATCH it will work. i.e delete TRY CATCH (Line 20 and Line 48 - 51)
I've realised I've buried the answer in the below, so I'll bring it up to the top to make it clearer: you cannot wrap any flow control statements around an attempt to create or alter a procedure
A simpler example demonstrates the problem:
create procedure ABC
as
go
select * from sys.objects
alter procedure ABC as
Which produces the message:
Msg 111, Level 15, State 1, Procedure ABC, Line 2
'CREATE/ALTER PROCEDURE' must be the first statement in a query batch.
This is explicitly documented for CREATE PROCEDURE but for some reason isn't for ALTER PROCEDURE.
The CREATE PROCEDURE statement cannot be combined with other Transact-SQL statements in a single batch.
So the upshot is, you cannot wrap any flow control statements around an attempt to create or alter a procedure.
The reason is actually pretty simple - BEGIN and END aren't required around the body of the stored procedure - and a stored procedure can actually contain multiple "top-level" BEGIN/END pairs:
create procedure ABC as
begin
select * from sys.objects
end
begin
select * from sys.columns
end
is fine - so the only way that SQL Server knows the extent of a stored procedure, when it's being defined, is "from the CREATE/ALTER PROCEDURE until the end of the batch."

SQL Server Cursor Error

I want find the records which have ~ in the table.
I am using cursor to, but I am getting unexpected error.
Any help would be appreciated.
DECLARE #test VARCHAR(5000)
DECLARE #column_name VARCHAR(2000)
Declare #TABLE_NAME_MAIN VARCHAR(200)
SET #TABLE_NAME_MAIN = 'Ar_Receipt_Item_OHM'
DECLARE cur_name
CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TABLE_NAME_MAIN
OPEN cur_name
FETCH NEXT FROM cur_name INTO #column_name
WHILE ##Fetch_status = 0
BEGIN
SET #test = N'SELECT top 2 * FROM OHMPreStage.dbo.'+#TABLE_NAME_MAIN+' WHERE '+#column_name+' LIKE ''%~%'''
exec #test
FETCH NEXT FROM cur_name INTO #column_name
END
CLOSE cur_name
DEALLOCATE cur_name
SET NOCOUNT OFF
Error:
Msg 911, Level 16, State 4, Line 24
Database 'SELECT top 2 * FROM OHMPreStage' does not exist. Make sure that the name is entered correctly.
You just need to change:
exec #test
To
exec (#test)
EXEC is for executing stored procedures, EXEC() the function puts together a dynamic string and executes it.
You're probably going to have issues searching columns with "LIKE" That aren't string columns.
You should check out the answer to this question here, which gives you a script that scans all tables in a database for specific string content:
Selecting column names that have specified value
Just substitute your search string '%~%', and if you need to, limit the tables searched to just the one (or set of) tables you need.

How do I catch a specific exception type from within a stored procedure?

I am writing a stored procedure which iterates over all of the databases on the server and populates a table variable with an aggregate of the data from some of the different databases. Some databases I'm not interested in as they are irrelevant. The problem is when my CURSOR iterates through those databases I don't care about, a SELECT statement is issued on a table that doesn't exist. How can I ignore the Invalid object name exception and continue with my processing?
Edit:
Here is how I was attempting to skip over databases that were irrelevant:
DECLARE db_cursor CURSOR FOR
SELECT name
FROM MASTER.dbo.sysdatabases
WHERE name NOT IN ('master','model','msdb','tempdb')
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #currentDatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'SELECT COUNT(Name) FROM ' + #currentDatabaseName + '.sys.Tables WHERE Name = ''SomeTableICareAbout'''
INSERT INTO #tableSearchResult
EXEC sp_executesql #sql
SET #tableCount = (SELECT COUNT(*) FROM #tableSearchResult WHERE TableCount = 1)
--If the table I care about was found, then do the good stuff
IF #tableCount > 0
...
The problem with this approach is if the executing user (in my case a service account) does not have access to SELECT on the table, then I never know about that error. If the user doesn't have SELECT access, I want that exception to be raised. But, even if the user doesn't have SELECT access, it can SELECT on the sys.Tables view.
You can't catch error 208 directly because it's a name resolution error that is raised at compilation time and before the code is actually executed. The behaviour is documented: see the section called "Errors Unaffected by a TRY…CATCH Construct" for an explanation, and the answers to this question have some interesting comments.
In addition to the 'solution' in the documentation, you can use dynamic SQL; the error will be caught in this example:
begin try
exec('select * from dbo.ThisTableDoesNotExist');
end try
begin catch
select error_number();
end catch;
If you're looping through all databases, there's a good chance you're using dynamic SQL somewhere anyway, so this might suit your case better.
You can catch the error if you are doing it inside a stored procedure (Example documented Here: http://msdn.microsoft.com/en-us/library/ms175976.aspx
Also you can change your dynamic sql to do something like this
SET #sql = '
If Exists(Select Name From ' + #currentDatabaseName + '.sys.Tables
WHERE Name = ''SomeTableICareAbout'')' --+
--Add Whatever the Good Stuff is
EXEC sp_executesql #sql
But checking if the table exists first, instead of doing the select count(1) from the table, will prevent that error from being raised.

Resources