T-SQL Stored Procedure with Cursor Loop causing Errors - sql

Could someone please explain to me why I have error message show up when I exec the stored procedure. "A cursor with the name 'tName_cursor' already exists"
DECLARE #tName VARCHAR(100)
DECLARE #lsql VARCHAR(8000)
DECLARE tName_cursor CURSOR FOR
SELECT NAME FROM SYS.tables WHERE TYPE = 'U' AND NAME LIKE 'PPM_METRIC%'
OPEN tName_cursor;
FETCH NEXT FROM tName_cursor INTO #tName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #lsql = N'
UPDATE [' +#tName+ ']
SET LATESTOFALL_FLG = ''N''
FROM [' +#tName+ '] T
JOIN D_CUSTOM_METRICS_RULE S
ON T.METRIC_ID = S.CUSTOM_METRIC_RULE_ID
AND T.LATESTOFALL_FLG = ''Y'''
EXECUTE sp_executesql #lsql
FETCH NEXT FROM tName_cursor INTO #tName;
END
CLOSE tName_cursor;
DEALLOCATE tName_cursor;
DECLARE tName_cursor_REDO CURSOR FOR
SELECT NAME FROM SYS.tables WHERE TYPE = 'U' AND NAME LIKE 'PPM_METRIC%'
OPEN tName_cursor_REDO;
FETCH NEXT FROM tName_cursor_REDO INTO #tName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #lsql = N'
UPDATE [' +#tName+ ']
SET LATESTOFDAY_FLG = ''N''
FROM [' +#tName+ '] T
JOIN D_CUSTOM_METRICS_RULE S
ON T.METRIC_ID = S.CUSTOM_METRIC_RULE_ID
AND T.CALC_METRIC_DATE_ID = CONVERT(INT,convert(VARCHAR, getdate(), 112))
AND T.LATESTOFDAY_FLG = ''Y'''
EXECUTE sp_executesql #lsql
FETCH NEXT FROM tName_cursor_REDO INTO #tName;
END
CLOSE tName_cursor_REDO;
DEALLOCATE tName_cursor_REDO;

Looks like some exception or 'return' breaks execution before DEALLOCATE command
"A cursor with the name 'tName_cursor' already exists"
message can happen when your 'declare' block is executed successfully, but after some code fails before 'DEALLOCATE tName_cursor' statement. Then, the second time you execute stored proc, it tries to declare a cursor again and the error message pops out. I recommend you to add 'begin try .. end try' block to your code and 'print ERROR_MESSAGE()' in exception block to see what comes out.

It sounds like you may be using GLOBAL cursors (?).
If you don't need them, I suggest using LOCAL cursors.
e.g.
DECLARE tName_cursor_REDO CURSOR LOCAL FOR
SELECT NAME FROM SYS.tables WHERE TYPE = 'U' AND NAME LIKE 'PPM_METRIC%'
That may help to make things more robust.

Related

Unable to Modify User-Defined Table Type

I have a SQL User-Defined Table Type. It used in many
stored procedures.Now i need to change a column in that table type.
I tried to drop and recreate the User-Defined Table Type.But SQL Server
doesn't Allow that. It shows up following error.
Msg 3732, Level 16, State 1, Line 3
Cannot drop type 'dbo.UserDefinedTableType' because it is being referenced by object 'SP_DoSomething'. There may be other objects that reference this type.
Msg 219, Level 16, State 1, Line 3
The type 'dbo.UserDefinedTableType' already exists, or you do not have permission to create it.
How to alter the User-Defined Table Type without modifying all the Stored procedure that uses User-Defined Table Type ?
You have binding in SP_DoSomething stored procedure. The type you want to change is used in that stored procedure.
You need to save script of that procedure. Drop it. Change dbo.UserDefinedTableType and create procedure again.
There is a similar post here. Check is some of the answers can help you. Answer of #norlando seems promising.
In total you should delete all Functions and Stored Procedures which use this User-Defined Table Type. Then you can drop User-Defined Table Type and recreate it. Then you should recreate all Stored Procedures and Functions which you deleted in previous step.
You can use this command for drop and recreate all SPs and Functions.
I suggest you to run this command with Print line to create Drop(s) and Create(s) command. Then you can put between Drop(s) command and Create(s) command your modification.
Declare #fullObjectName NVarChar(1000) = 'ref.Employee'
Declare #CreateCommand VarChar(Max), #DropCommand VarChar(Max)
Declare #ProcList Table
(
RowId Int,
CreateCommand NVarChar(Max),
DropCommand NVarChar(Max)
)
Insert Into #ProcList
SELECT ROW_NUMBER() OVER (ORDER BY OBJECT_NAME(m.object_id)) RowId,
definition As CreateCommand,
'DROP ' +
CASE OBJECTPROPERTY(referencing_id, 'IsProcedure')
WHEN 1 THEN 'PROC '
ELSE
CASE
WHEN OBJECTPROPERTY(referencing_id, 'IsScalarFunction') = 1 OR OBJECTPROPERTY(referencing_id, 'IsTableFunction') = 1 OR OBJECTPROPERTY(referencing_id, 'IsInlineFunction') = 1 THEN 'FUNCTION '
ELSE ''
END
END
+ SCHEMA_NAME(o.schema_id) + '.' +
+ OBJECT_NAME(m.object_id) As DropCommand
FROM sys.sql_expression_dependencies d
JOIN sys.sql_modules m
ON m.object_id = d.referencing_id
JOIN sys.objects o
ON o.object_id = m.object_id
WHERE referenced_id = TYPE_ID(#fullObjectName)
-----
Declare cur_drop SCROLL Cursor For Select CreateCommand, DropCommand From #ProcList
OPEN cur_drop
Fetch Next From cur_drop Into #CreateCommand, #DropCommand
While ##FETCH_STATUS = 0
Begin
--Exec sp_executesql #DropCommand
PRINT #DropCommand
Fetch Next From cur_drop Into #CreateCommand, #DropCommand
End
/*
Drop And ReCreate User Defined Table Type
*/
Fetch First From cur_drop Into #CreateCommand, #DropCommand
While ##FETCH_STATUS = 0
Begin
--Exec sp_executesql #CreateCommand
PRINT #CreateCommand
Fetch Next From cur_drop Into #CreateCommand, #DropCommand
End
Close cur_drop
Deallocate cur_drop
The code below while incomplete should be a good start. Please note that among many other things:
you must adapt it (I am using user type, not table type) and test it.
It only handles procs.
If your procs definition start with alter, you need to add code and logic to control this and deal with it in the cursor (create empty proc first then alter).
Using it will also remove all granted rights on the procs.
...
Begin Try
Begin Tran
Declare #procs Table(code nvarchar(max), pname sysname, pschema sysname)
Declare #sql nvarchar(max), #code nvarchar(max), #pname sysname, #pschema sysname
Declare cur_drop Cursor For
Select sp.definition, obj.name, schema_name(obj.schema_id) From sys.sql_modules as sp
Inner Join sys.objects as obj on obj.object_id = sp.object_id
Inner Join sys.dm_sql_referencing_entities ('dbo.TestType', 'TYPE') as dep on dep.referencing_id = sp.object_id
Where obj.Type = 'P'
Open cur_drop
Fetch Next From cur_drop Into #code, #pname, #pschema
While ##FETCH_STATUS = 0
Begin
Print 'Drop '+#pname
Insert into #procs(code, pname, pschema) Select #code, #pname, #pschema
Set #sql = 'Drop proc ['+#pschema+'].['+#pname+']'
Exec sp_executesql #sql
Fetch Next From cur_drop Into #code, #pname, #pschema
End
Close cur_drop
Deallocate cur_drop
-- Drop Type
-- Create Type
Declare cur_create Cursor For
Select code, pname, pschema From #procs
Open cur_create
Fetch Next From cur_create Into #code, #pname, #pschema
While ##FETCH_STATUS = 0
Begin
Print 'Create '+#pname
Exec sp_executesql #code
Fetch Next From cur_create Into #code, #pname, #pschema
End
Close cur_create
Deallocate cur_create
Commit
End Try
Begin Catch
rollback;
throw;
End Catch
You could automate the process of temporary deleting the dependencies and then re-creating them, so you shouldn't bother if you have many dependencies. For insrutctions on how to automate this process, see my answer here.

Drop databases with no tables

Is there a query which drops / deletes databases with no tables in them (deletes empty databases)?
Server is Microsoft SQL Server 2005
This should do it.
Tested on a lab machine and it dropped all databases with 0 user tables.
Note, however, that tables aren't the only things in a database, necessarily. There could be stored procedures, functions, etc that someone might still need.
NOTE THAT THIS IS A VERY DANGEROUS OPERATION, AS IT DROPS DATABASES. USE AT YOUR OWN RISK. I AM NOT RESPONSIBLE FOR DAMAGE YOU CAUSE.
USE [master];
DECLARE #name varchar(50);
DECLARE #innerQuery varchar(max);
DECLARE tableCursor CURSOR FOR SELECT name FROM sys.databases where owner_sid != 0x01;
OPEN tableCursor;
FETCH NEXT FROM tableCursor
INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #innerQuery = 'USE [' + #name + ']; IF (SELECT COUNT(*) FROM sys.objects WHERE type = ''U'') = 0
BEGIN
USE [master];
DROP DATABASE [' + #name + ']
END'
EXEC(#innerQuery)
FETCH NEXT FROM tableCursor INTO #name
END
CLOSE tableCursor;
DEALLOCATE tableCursor;
Note also that, if a database is in use, SQL Server will refuse to drop it. So, if there are other connections to a particular database that this tries to drop, the command will abort.
To avoid that problem, you can set the database in question to single-user mode.
The following script is the same as the above, except it also sets the target databases to single-user mode to kill active connections.
BE EVEN MORE CAREFUL WITH THIS, AS IT'S ESSENTIALLY THE NUCLEAR OPTION:
use [master];
DECLARE #name varchar(50);
DECLARE #innerQuery varchar(max);
DECLARE tableCursor CURSOR FOR SELECT name FROM sys.databases where owner_sid != 0x01;
OPEN tableCursor;
FETCH NEXT FROM tableCursor
INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #innerQuery =
'USE [' + #name + '];
IF (SELECT COUNT(*) FROM sys.objects WHERE type = ''U'') = 0
BEGIN
USE [master];
ALTER DATABASE [' + #name + '] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [' + #name + '];
END'
EXEC(#innerQuery)
FETCH NEXT FROM tableCursor INTO #name
END
CLOSE tableCursor;
DEALLOCATE tableCursor;
I think it is not possible (at least not with a TSQL command, might be a stored procedure somewhere). You would have to query sysobjects and abort if any found. (And you have to decide if you want to ignore some of the system objects like the design tables).
The script below will generate the needed DROP DATABASE script. You could tweak this to execute the statement too (in the master database context) but I suggest you review it first just in case.
EXEC sp_MSforeachdb N'
USE [?];
IF N''?'' NOT IN(N''master'', N''model'', N''msdb'', N''tempdb'')
BEGIN
IF NOT EXISTS(
SELECT *
FROM sys.tables
WHERE
OBJECTPROPERTYEX(object_id, ''IsMSShipped'') = 0
)
BEGIN
PRINT ''DROP DATABASE [?];'';
END;
END;';

sp_MSForEachDB doesn't seem to like GO

I have used this SP before. Now, I am trying to permission a user to 50 odd databases that start with the same letters, using the code below. It looks like it does not like "GO" in the code. Why is that ? and what is the work around?
Thanks for your time.. :)
RM
exec sp_MSForEachDB
'
IF ''?'' LIKE ''MYDBNames%''
BEGIN
Use [?]
Go
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts]
GO
EXEC sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts''
GO
END
'
I just explained this in another question yesterday (here). This essence is this: GO isn't a SQL statement, it's an SSMS/SQLCMD command that is used to separate batches (groups of SQL statements that are compiled together). So you cannot use it in things like stored procedures or Dynamic SQL. Also, very few statement contexts can cross over a GO boundary (transactions and session-level temp tables are about it).
However, because both stored procedures and Dynamic SQL establish their own separate batches/execution contexts, you can use these to get around the normal need for GO, like so:
exec sp_MSForEachDB
'
IF ''?'' LIKE ''MYDBNames%''
BEGIN
Use [?]
EXEC(''
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts]
'')
EXEC('' EXEC sp_addrolemember N''''db_owner'''', N''''MYDOMAIN\Analysts'''' '')
END
'
The word GO is a batch separator and is not a SQL keyword. In SSMS you can go to options and change it to anything - COME for example.
Try this:
exec sp_MSForEachDB
'
IF ''?'' LIKE ''MYDBNames%''
BEGIN;
Use [?];
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts];
EXEC sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts'';
END;'
Just stop using GO, it's not necessary (and it's not even T-SQL; it's just a batch separator for Management Studio).
Stop using sp_MSForEachDB. Oh, the problems (if you want proof, see here, here and here).
Here is code that does this without using any stored procedure:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += N'USE ' + QUOTENAME(name) + ';
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts];
EXEC sys.sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts'';
-- actually should use ALTER ROLE now
'
FROM sys.databases
WHERE state = 0 -- online
AND name LIKE N'MyDBName%';
PRINT #sql;
-- EXEC sys.sp_executesql #sql;
Like #RBarryYoung's answer, an embedded exec() can be used to avoid needing a "go" when the command carries the requirement that it must be the "first within a batch." I don't think the question's example needs a "go" but the headline itself may lead people here.
Here is a basic SP to wrap this that support's DDL statements like "create proc" and provides some error messages. I'm probably the ~5th person to do this and this one isn't the end all.
Blessing/Curse: There is a call to Common.ufn_UsvToNVarcharKeyTable() which you may need to patch in your own CSV splitter or remove:
alter proc [Common].[usp_ForEachDatabase]
#pSql nvarchar(max),
#pDatabaseNameLikeOpt nvarchar(300) = null, -- Optional pattern to match with like
#pDatabaseNameCsvOpt nvarchar(max) = null, -- Optional list of DBs
#pJustPrintDbNames bit = 0, -- Don't exec, just print database names
#pDebug bit = 1 -- may add additional print statements
as
/*-----------------------------------------------------------------------------------------------------------------
Purpose: Execute SQL on each database. Replacement for the standard sp_MSForEachDB which has a size character
limit and requires an exec() within for DDL commands which must be the first in a batch.
Sources: Ideas from http://www.experts-exchange.com/Database/MS-SQL-Server/SQL-Server-2005/Q_26337388.html
http://stackoverflow.com/questions/20125430
http://stackoverflow.com/questions/1819095/
Modified By Description
---------- ---------- -----------------------------------------------------------------------------------
2014.10.14 crokusek Initial version. Pieces from internet.
--------------------------------------------------------------------------------------------------------------*/
begin try
declare databaseCursor cursor local forward_only static for
select IsNull(sd.Name, ud.Name) as Name,
--
-- If a list was specified, flag when a name was not found
--
convert(bit, iif(sd.Name is null, 1, 0)) as IsNotFound
from
(
select Name from sys.databases
where Name not in ('master', 'tempdb', 'model', 'msdb')
and is_distributor = 0 -- http://stackoverflow.com/questions/1819095/
) sd
full outer join ( select Value as Name from Common.ufn_UsvToNVarcharKeyTable(#pDatabaseNameCsvOpt, ',' ) ) ud
on ud.Name = sd.Name
where (#pDatabaseNameLikeOpt is null or IsNull(sd.Name, ud.Name) like #pDatabaseNameLikeOpt)
and (#pDatabaseNameCsvOpt is null or ud.Name is not null)
order by IsNull(sd.Name, ud.Name);
declare
#matchingDatabaseNames nvarchar(max),
#databaseName nvarchar(300),
#isNotFound bit,
#errorCount int = 0,
#successCount int = 0,
--
-- Use an embedded exec() to place the user command(s) in a separate context to avoid errors like:
-- CREATE/ALTER PROCEDURE must be the first statement in a query batch.
--
#sqlExec nvarchar(max) = N'exec (''' + replace(#pSql, '''', '''''') + ''')';
open databaseCursor
fetch next from databaseCursor into #databaseName, #isNotFound;
while ##fetch_status = 0
begin
if (#isNotFound = 1)
begin
print 'Error: Database ' + #databaseName + ' was not found.';
set #errorCount += 1;
end
else
begin
set #matchingDatabaseNames = coalesce(#matchingDatabaseNames + ',', '') + #databaseName; -- Create Csv
print 'Database: ' + #databaseName;
if (#pJustPrintDbNames = 0)
begin
declare
#execSql nvarchar(max) = 'use ' + #databaseName + ';' + char(10) + #sqlExec;
begin try
exec (#execSql)
set #successCount += 1;
end try
begin catch
select #databaseName as [Database],
error_number() as ErrorNumber,
error_severity() as ErrorSeverity,
error_state() as ErrorState,
error_procedure() as ErrorProcedure,
error_line() as ErrorLine,
error_message() as ErrorMessage;
set #errorCount += 1;
end catch
end
end
fetch next from databaseCursor into #databaseName, #isNotFound;
end
if (#pJustPrintDbNames = 1)
print #matchingDatabaseNames; -- this can then be used as input
else
print 'Completed with ' + convert(varchar(30), #errorCount) + ' Errors ' +
'and ' + convert(varchar(30), #successCount) + ' Successes.';
end try
begin catch
if (xact_state() = -1)
rollback;
-- Log / Rethrow
end catch
go
Usage:
declare
#sql nvarchar(max) = N'
create proc usp_ReplaceEachSingleQuoteWithTwoSingleQuotesInTheDefinition
...';
exec [Common].[usp_ForEachDatabase]
#pSql = #sql,
#pDatabaseNameLikeOpt = null,
#pDatabaseNameCsvOpt = null,
#pJustPrintDbNames = 0,
#pDebug = 1;
Why not use a cursor?
They are great for management tasks like this. As long as the record set is small, they perform well.
The code below creates a SQL statement for each database that uses the ; to combine multiple tasks.
Unlike the code above, it remove any existing users before creating a new one. Avoids a possible issue.
Then it executes the code. If you want, you could even log these adhoc requests to a internal table and add error checking.
Good luck.
--
-- EXEC same statements against several like databases
--
-- Declare local variables
DECLARE #STMT NVARCHAR(4000);
-- Allocate cursor, return table names
DECLARE MYTRG CURSOR FAST_FORWARD FOR
SELECT
' use [' + ltrim(rtrim(d.name)) + ']; ' +
' IF EXISTS (SELECT * FROM sys.database_principals WHERE name = N''[MYDOMAIN\Analysts]'') DROP USER [MYDOMAIN\Analysts]; ' +
' CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts] WITH DEFAULT_SCHEMA=[DBO]; ' +
' sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts''; ' as STMT
FROM master.dbo.sysdatabases d
WHERE d.name like 'MYDBNames%'
ORDER BY d.name;
-- Open cursor
OPEN MYTRG;
-- Get the first row
FETCH NEXT FROM MYTRG INTO #STMT;
-- While there is data
WHILE (##FETCH_STATUS = 0)
BEGIN
-- Show detail database info
EXEC sp_executesql #STMT;
-- Get the first row
FETCH NEXT FROM MYTRG INTO #STMT;
END;
-- Close the cursor
CLOSE MYTRG;
-- Release the cursor
DEALLOCATE MYTRG;

calling stored procedure in a cursor

I have created a SQL Server 2005 stored procedure which finds dependent objects on a particular table.
I want to run this stored procedure for different database and for different tables. I have created cursor for this.
When I write USE #dbname, it tries to find the stored procedure in a #dbname and not the current database.
Can anybody please help me with how do I write this command in a cursor?
DECLARE name_cur CURSOR FOR
SELECT db_name, obj_name from Stats_Usage
WHERE last_user_update > '2011-06-01' ORDER BY db_name
DECLARE #tableName NVARCHAR (800)
DECLARE #dbName NVARCHAR(800)
DECLARE #sql NVARCHAR(900)
OPEN name_cur
FETCH name_cur INTO #dbName, #tableName
WHILE ##Fetch_Status = 0
BEGIN
SET #sql = 'USE '+#dbName +' EXEC proc_depend ' + #tableName
EXEC (#sql)
FETCH name_cur INTO #dbName, #tableName
END
CLOSE name_cur
DEALLOCATE name_cur
GO
You can fully qualify your Stored Procedure name.
Assuming the database your SP resides in is called procs (for example), you could amend your query to use the following:
SET #sql = 'USE '+#dbName +' EXEC procs.dbo.proc_depend ' + #tableName
EXEC (#sql)
Refactor your stored proc to check for dependant objects cross database. You'll want to send it a command like this:
exec proc_depend 'MyDatabase.dbo.MyTable';
Try this instead:
SET #sql = ' EXEC proc_depend ''' #dbName + '.dbo.'+ #tableName + ''';
You'll need to dig into & modify proc_depend to ensure that it can take a fully qualified object name like database.schema.table

How do I go through a cursor to perform logic on multiple tables? (The table names are in the cursor)

I get the feeling this is pretty basic database work, but it isn't for me. I'm trying to get a list of all of my tombstone tables from system tables and store the results in a cursor. I'm then trying to perform some logic on each of those tables I'm having trouble doing so.
Any help would be greatly appreciated.
Here is the error I get:
Must declare the table variable "#tablename"
Here is the code:
declare tombstonetables cursor for
(select name from sys.objects
where
name like'%tombstone%'
and type = 'U'--for user_table
)
Print 'Begin purging tombstone tables'
declare #tablename varchar(250)
open tombstonetables
fetch next from tombstonetables into #tablename
WHILE ##FETCH_STATUS = 0
begin
select * from #tablename--real logic goes here later
fetch next from tombstonetables into #tablename
end
close tombstonetables
deallocate tombstonetables
Looks like you need to use Dynamic SQL
Here is a reference to a simple walk through http://www.mssqltips.com/tip.asp?tip=1160
You will probably need to make use of sp_executesql
Here is a simple example of using Dynamic SQL with your example
DECLARE #DynamicSQL nvarchar(100)
WHILE ##FETCH_STATUS = 0
begin
SET #DynamicSQL = 'select * from ' + #tablename --real logic goes here later
EXEC #DynamicSQL
fetch next from tombstonetables into #tablename
end