How to do an Alter in SQL Cursor - sql

I have 100's of databases for which I need to do an Alter to a procedure, they all have the same procedure. How can I add a cursor that will allow me to do this alter?.
DECLARE #databasename varchar(100)
DECLARE #Command nvarchar(200)
DECLARE database_cursor CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
OPEN database_cursor
FETCH NEXT FROM database_cursor INTO #databasename
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Command = 'ALTER PROCEDURE ''' + #databasename + '''.[dbo].[DeleteAccountUpgrade]
#Id INT
AS
DELETE FROM AccountUpgrades WHERE Id = #Id'
EXEC sp_executesql #Command
FETCH NEXT FROM database_cursor INTO #databasename
END
CLOSE database_cursor
DEALLOCATE database_cursor
It says that i need to declare #Id but i dont understand why, I just want to run that alter on each database, i am not sending any data to execute the procedure, i just want to modify the existing procedure on all databases.

Use nest sp_executesql calls. This allow you to execute DDL against other databases.
DECLARE #databasename varchar(100)
DECLARE #Command nvarchar(200)
DECLARE database_cursor CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
OPEN database_cursor
FETCH NEXT FROM database_cursor INTO #databasename
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Command = N'USE ' + QUOTENAME(#databasename) +
' EXEC sp_executesql N''ALTER PROCEDURE [dbo].[DeleteAccountUpgrade] ' +
'#Id INT ' +
'AS ' +
'DELETE FROM AccountUpgrades WHERE Id = #Id'''
--PRINT #Command
EXEC sp_executesql #Command
FETCH NEXT FROM database_cursor INTO #databasename
END
CLOSE database_cursor
DEALLOCATE database_cursor

You need to use EXEC by itself, and not with sp_executesql.
EXEC sp_executesql #Command
should be changed to:
EXEC(#Command)
The sp_executesql procedure does parameterization with variables. This is where the error is coming from. See also https://stackoverflow.com/a/16308447/1822514

Related

Like all databases on the server for a query

I have a statement but it queries a database that I insert in the variable "USE", I need it to read all the databases hosted in sys.databases:
USE [BD001]
SELECT DB_NAME() AS DatabaseName,
'guest' AS Database_User,
[permission_name],
[state_desc]
FROM sys.database_permissions
WHERE [grantee_principal_id] = DATABASE_PRINCIPAL_ID('guest')
AND [state_desc] LIKE 'GRANT%'
AND [permission_name] = 'CONNECT'
AND DB_NAME() NOT IN ('master', 'tempdb', 'msdb');
I thought I could create a variable but I can't...and I can't figure out how to do it.
Is there any way to make this query generic so that I can insert other test queries?
DECLARE #DB_Name varchar(100)
DECLARE #Command nvarchar(1000)
DECLARE database_cursor CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
WHERE name NOT IN ('master', 'tempdb', 'msdb' )
OPEN database_cursor
FETCH NEXT FROM database_cursor INTO #DB_Name
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Command = '
SELECT ''' + #DB_Name + ''' AS DatabaseName,
''guest'' AS Database_User,
[permission_name],
[state_desc]
FROM ' +#DB_Name+ '.sys.database_permissions
WHERE [grantee_principal_id] = DATABASE_PRINCIPAL_ID(''guest'')
AND [state_desc] LIKE ''GRANT%''
AND [permission_name] = ''CONNECT'''
EXEC sp_executesql #Command
FETCH NEXT FROM database_cursor INTO #DB_Name
END
CLOSE database_cursor
DEALLOCATE database_cursor
I think the following cursor may help you;
DECLARE #DB_Name varchar(100)
DECLARE #Command nvarchar(1000)
DECLARE database_cursor CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
WHERE name NOT IN ('master', 'tempdb', 'msdb' )
OPEN database_cursor
FETCH NEXT FROM database_cursor INTO #DB_Name
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Command = '
SELECT ''' + #DB_Name + ''' AS DatabaseName,
''guest'' AS Database_User,
[permission_name],
[state_desc]
FROM ' +#DB_Name+ '.sys.database_permissions
WHERE [grantee_principal_id] = DATABASE_PRINCIPAL_ID(''guest'')
AND [state_desc] LIKE ''GRANT%''
AND [permission_name] = ''CONNECT'''
EXEC sp_executesql #Command
FETCH NEXT FROM database_cursor INTO #DB_Name
END
CLOSE database_cursor
DEALLOCATE database_cursor

Need to declare a cursor with a variable in the declaration

I had a problem with a maintenance procedure and need to create a second one where I declare a cursor with a list of database ID and pass them into another cursor to get a list of tables for each database.
Current problem is that in the inner cursor even though it runs use [database_name], when i declare it and specify my query it selects the tables from the master database. it doesn't change the database context before going into the inner cursor.
DECLARE #db varchar(128)
declare #cmd varchar(1024)
declare #table varchar(255)
declare #cmd2 varchar(1024)
DECLARE crDB CURSOR global FORWARD_only FOR
SELECT [name] FROM sys.databases WHERE database_id > 4
and database_id in (33) ORDER BY [name]
OPEN crDB
FETCH crDB INTO #db
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd = 'USE [' + #db +']'
EXEC (#cmd)
DECLARE crTB CURSOR LOCAL FAST_FORWARD FOR
select [name] from sys.objects where type = 'u' ORDER BY [name]
OPEN crTB
FETCH crTB INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd2 = 'Update Statistics ' + #table + CHAR(13)
PRINT #cmd2
EXEC (#cmd2)
end
CLOSE crTB
DEALLOCATE crTB
FETCH crDB INTO #db
END
CLOSE crDB
DEALLOCATE crDB
GO
The issue with your inner cursor, is scope. You can to do 2 things here. You have to move your inner cursor to right after the USE [' + #db like:
DECLARE #db VARCHAR(128);
DECLARE #cmd VARCHAR(1024);
DECLARE #table VARCHAR(255);
DECLARE #cmd2 VARCHAR(1024);
DECLARE crDB CURSOR GLOBAL READ_ONLY FORWARD_ONLY FOR
SELECT name
FROM sys.databases
WHERE database_id > 4
AND database_id IN (33)
ORDER BY name;
OPEN crDB;
FETCH crDB
INTO #db;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd = 'USE [' + #db + ']
GO;
DECLARE crTB CURSOR LOCAL FAST_FORWARD FOR
SELECT name
FROM sys.objects
WHERE type = ''u'';
ORDER BY name;
OPEN crTB;
FETCH NEXT FROM crTB
INTO #table;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd2 = ''Update Statistics '' + #table + CHAR(13);
PRINT #cmd2;
EXEC (#cmd2);
END;
CLOSE crTB;
DEALLOCATE crTB;
';
EXEC (#cmd);
FETCH NEXT FROM crDB
INTO #db;
END;
CLOSE crDB;
DEALLOCATE crDB;
Or you can get rid of the inner cursor altogether and use sys.sp_MSforeachtable:
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd = 'USE [' + #db + ']
GO;
EXEC sys.sp_MSforeachtable #command1 = ''UPDATE STATISTICS ?;''';
EXEC (#cmd);

cursors within cursors for Auto_Fix users

I have the below script and I want to be able to run this against a dynamic list of databases except the system databases. That's straight forward enough. The tricky bit is each database could have a different list of users to run the fix command against. Would this be a 3rd cursor? My attempt below which is not properly populating the users for each database. Any help would be greatly appreciated.
SET nocount ON
GO
SET QUOTED_IDENTIFIER OFF
GO
--
-- Declare and define variables
--
DECLARE #databasename VARCHAR(50) -- database name
DECLARE #sqlcommand nvarchar(256) -- SQL Command generated
-- Include the in-scope database names into #name
DECLARE db_cursor CURSOR FOR
SELECT NAME
FROM master.dbo.sysdatabases
WHERE NAME NOT IN ( 'master', 'model', 'msdb', 'tempdb', 'DBATools' ) -- don't include the databases
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #databasename
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Fixing Logins for '
+ Cast(#databasename AS VARCHAR)
DECLARE curSQL CURSOR FOR
SELECT "USE " + ( #databasename ) + ";" + " exec sp_change_users_login 'AUTO_FIX','" + NAME + "'"
SELECT NAME
FROM sys.sysusers
WHERE issqluser = 1
AND NAME NOT IN ( 'dbo', 'guest', 'INFORMATION_SCHEMA', 'sys' )
OPEN curSQL
FETCH curSQL INTO #sqlcommand
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #sqlcommand
EXEC (#sqlcommand)
FETCH curSQL INTO #sqlcommand
END
CLOSE curSQL
DEALLOCATE curSQL
FETCH NEXT FROM db_cursor INTO #databasename
END
CLOSE db_cursor
DEALLOCATE db_cursor
Or you can do it cursorless.
DECLARE #Output NVARCHAR(MAX)
SET #Output = ''
SELECT #Output = #Output +
'--Fixing Logins For ' + name + CHAR(10) +
'USE ' + name + CHAR(10) +
'EXEC sp_change_users_login ''AUTO_FIX'','''+
(
SELECT b.name
FROM sys.sysusers as b
WHERE issqluser = 1
AND b.name NOT IN ( 'dbo', 'guest', 'INFORMATION_SCHEMA', 'sys' )
)
+''''+CHAR(10)+CHAR(10)
FROM master.dbo.sysdatabases
WHERE NAME NOT IN ( 'master', 'model', 'msdb', 'tempdb', 'DBATools' )
PRINT #Output
The main thing with your procedure is that, unlike PHP, TSQL cannot use single and double quotes interchangeably. So I fixed up your dynamic SQL string.
The other minor thing appears to have been a disjointed query from the cursor declaration - in yours, you follow the second declare with a select clause, without an accompanying from clause, which would have thrown on "Name" not being able to be found for that cursor. So I fixed that up, too.
DECLARE #databasename SYSNAME, #sqlcommand nvarchar(max)
DECLARE db_cursor CURSOR FOR
SELECT NAME
FROM master.dbo.sysdatabases
WHERE NAME NOT IN ( 'master', 'model', 'msdb', 'tempdb', 'DBATools' ) -- don't include the databases
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #databasename
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Fixing Logins for '
+ Cast(#databasename AS VARCHAR)
DECLARE curSQL CURSOR FOR
SELECT 'USE ' + #databasename + ';' + ' exec sp_change_users_login ''AUTO_FIX'',''' + NAME + ''';'
FROM sys.database_principals
WHERE Type='S'
AND NAME NOT IN ( 'dbo', 'guest', 'INFORMATION_SCHEMA', 'sys' )
OPEN curSQL
FETCH curSQL INTO #sqlcommand
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #sqlcommand
--EXEC (#sqlcommand)
FETCH curSQL INTO #sqlcommand
END
CLOSE curSQL
DEALLOCATE curSQL
FETCH NEXT FROM db_cursor INTO #databasename
END
CLOSE db_cursor
DEALLOCATE db_cursor
EDIT: Switched table to database_principals rather than sys.sysusers per comment.

create function in another database dynamically

I'm trying to create function on every database while I'm on master,
I'm using cursors
DECLARE c_db_names CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN('master','model','msdb','tempdb','ReportServer$SQLEXPRESS','ReportServer$SQLEXPRESSTempDB')
OPEN c_db_names
FETCH next from c_db_names INTO #db_name
WHILE ##Fetch_Status = 0
BEGIN
set #sql = 'Create function...'
set #UseAndExecStatment = 'use ' + #db_name + ' exec sp_executesql '+#sql
exec Sp_executeSql #UseAndExecStatment
FETCH next from c_db_names INTO #db_name
END
CLOSE c_db_names
DEALLOCATE c_db_names
the problem is that its not working, it says:
"CREATE FUNCTION' must be the first statement in a query batch"
Any ideas?
Thanks for your help.
You can use db..sp_executesql instead:
declare #sql nvarchar(max), #db_name nvarchar(max), #sql2 nvarchar(max)
DECLARE c_db_names CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN('master','model','msdb','tempdb','ReportServer$SQLEXPRESS','ReportServer$SQLEXPRESSTempDB')
and name ='reports'
OPEN c_db_names
FETCH next from c_db_names INTO #db_name
WHILE ##Fetch_Status = 0
BEGIN
set #sql2 = 'create procedure ...'
set #sql = 'exec '+#db_name+'..sp_executesql N''' + #sql2+''''
print #sql
--exec(#sql)
FETCH next from c_db_names INTO #db_name
END
CLOSE c_db_names
DEALLOCATE c_db_names
define the database in "create" query
try
DECLARE c_db_names CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN('master','model','msdb','tempdb','ReportServer$SQLEXPRESS','ReportServer$SQLEXPRESSTempDB')
OPEN c_db_names
FETCH next from c_db_names INTO #db_name
WHILE ##Fetch_Status = 0
BEGIN
set #sql = 'Create function '+#db_name+'dbo.functionName ......'
exec Sp_executeSql #sql
FETCH next from c_db_names INTO #db_name
END
CLOSE c_db_names
DEALLOCATE c_db_names
see:
set #sql = 'Create function '+#db_name+'dbo.functionName ......'
done :)
declare #sql nvarchar(max), #db_name nvarchar(max), #sql2 nvarchar(max)
declare #sp_executesql nvarchar (max)
DECLARE c_db_names CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN('master','model','msdb','tempdb','ReportServer$SQLEXPRESS','ReportServer$SQLEXPRESSTempDB')
--and name ='reports'
OPEN c_db_names
FETCH next from c_db_names INTO #db_name
WHILE ##Fetch_Status = 0
BEGIN
set #sql2 = N'
CREATE FUNCTION [String_Turn_Month] (#d char(6))
RETURNS char(6)
AS
BEGIN
DECLARE ##DSTR char(6)
IF #D=''''000000''''
set ##dstr=''''000000''''
IF substring(#d,3,4)<1800
RETURN(#D)
IF substring(#d,3,4)>1800
set ##dstr=cast(substring(#d,3,4)+substring(#d,1,2) as char(6))
RETURN(##DSTR)
END'
-- set #sql = 'exec '+#db_name+'..sp_executesql N''' + #sql2+''''
set #sp_executesql = 'exec ' + #db_name + '..sp_executeSQL N''' + #sql2 + ''''
PRINT #sp_executesql
EXEC sp_executeSQL #sp_executesql
print '------------'
print '------------'
print '------------'
-- exec(#sql)
FETCH next from c_db_names INTO #db_name
END
CLOSE c_db_names
DEALLOCATE c_db_names

execute stored procedure in remote server with variable names

I have a cursor and its fetching database name and linked server name to execute a procedure in each of them
Declare getDBLinkServer cursor for
select DatabaseName, LinkServerName from DBnLinkServer
Open getDBLinkServer
fetch next from getDBLinkServer into #DatabaseName, #LinkServerName
While ##FETCH_STATUS = 0
Begin
exec #LinkServerName.#DatabaseName.dbo.someProcedure 'some'
fetch next from getDBLinkServer into #DatabaseName, #LinkServerName
End
Close getDBLinkServer
Deallocate getDBLinkServer
This is showing Incorrect syntax near '.' How can I avoid it?
Use dynamic sql. Like this:
declare #sql nvarchar(max)
set #sql = 'exec ' + #LinkServerName + '.' + #DatabaseName + '.dbo.'+#someProcedure+' ''some'''
exec sp_executeSQL #sql