cursors within cursors for Auto_Fix users - sql

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.

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

Displaying multiple database tables in one table

I have multiple databases with the same table (an Eventlog with different values). The names of these databases are subject to change. I am trying to display the Eventlog tables in one consolidated table with the corresponding database name.
I tried to using cursor and dynamic SQL statement to achieve this with no luck. As well, I'm not sure if that is the best approach. Would love some help!
-- Create a new table variable to record all the database name
DECLARE #Database_Table table ([TimeStamp] nvarchar(500)
,[EventIDNo] nvarchar(100)
,[EventDesc] nvarchar(1000))
--Create variable for database name and query variable
DECLARE #DB_Name VARCHAR(100) -- database name
DECLARE #query NVARCHAR(1000) -- query variable
--Declare the cursor
DECLARE db_cursor CURSOR FOR
-- Populate the cursor with the selected database name
SELECT name
FROM sys.databases
WHERE name NOT IN ('master','model','msdb','tempdb')
--Open the cursor
OPEN db_cursor
--Moves the cursor to the first point and put that in variable name
FETCH NEXT FROM db_cursor INTO #DB_Name
-- while loop to go through all the DB selected
WHILE ##FETCH_STATUS = 0
BEGIN
SET #query = N'INSERT INTO #Database_Table
SELECT #DB_Name, *
FROM ['+ #DB_Name +'].dbo.EventLog_vw as A'
EXEC (#query)
--Fetch the next record from the cursor
FETCH NEXT FROM db_cursor INTO #DB_Name
END
--Close and deallocate cursor
CLOSE db_cursor
DEALLOCATE db_cursor
SELECT *
FROM #Database_Table
If you need a single resultset and all tables have the same layout, this should work:
DECLARE #sql nvarchar(4000) =
(SELECT STRING_AGG(CONCAT(
'SELECT ''',
QUOTENAME(name),
''',
* FROM ',
QUOTENAME(name),
'..Table ',
CHAR(10)
), ' UNION ALL ' + CHAR(10))
FROM sys.databases);
SELECT #sql; -- for checking
EXEC(#sql);
If you're on compatibility level 130 or lower, you will have to use XML PATH(TYPE, '') to aggregate. I will leave that to you.
I notice a bug in this code:
SET #query = N'INSERT INTO #Database_Table
SELECT #DB_Name, *
FROM ['+ #DB_Name +'].dbo.EventLog_vw as A'
In the SELECT clause you reference #DB_Name inside the string itself without concatenating the value as a variable. You did do this correctly in the FROM clause.
Something like:
SET #query = N'INSERT INTO #Database_Table
SELECT ''' + #DB_Name + ''', *
FROM ['+ #DB_Name +'].dbo.EventLog_vw as A'

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);

How to do an Alter in SQL Cursor

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

How to drop all stored procedures at once in SQL Server database?

Currently we use separate a drop statements for each stored procedure in the script file:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MySP]')
AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[MySP]
Is there a way to drop them all at once, or maybe in a loop?
I would prefer to do it this way:
first generate the list of stored procedures to drop by inspecting the system catalog view:
SELECT 'DROP PROCEDURE [' + SCHEMA_NAME(p.schema_id) + '].[' + p.NAME + '];'
FROM sys.procedures p
This generates a list of DROP PROCEDURE statements in your SSMS output window.
copy that list into a new query window, and possibly adapt it / change it and then execute it
No messy and slow cursors, gives you the ability to check and double-check your list of procedure to be dropped before you actually drop it
Something like (Found at Delete All Procedures from a database using a Stored procedure in SQL Server).
Just so by the way, this seems like a VERY dangerous thing to do, just a thought...
declare #procName varchar(500)
declare cur cursor
for select [name] from sys.objects where type = 'p'
open cur
fetch next from cur into #procName
while ##fetch_status = 0
begin
exec('drop procedure [' + #procName + ']')
fetch next from cur into #procName
end
close cur
deallocate cur
In the Object Explorer pane, select the Stored Procedures folder.
Press F7 (or from the main menu, choose View > Object Explorer Details).
Select all procedures except the System Table.
Press Delete button and select OK.
You can delete Tables or Views in the same manner.
create below stored procedure in your db(from which db u want to delete sp's)
then right click on that procedure - click on Execute Stored Procedure..
then click ok.
create Procedure [dbo].[DeleteAllProcedures]
As
declare #schemaName varchar(500)
declare #procName varchar(500)
declare cur cursor
for select s.Name, p.Name from sys.procedures p
INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
WHERE p.type = 'P' and is_ms_shipped = 0 and p.name not like 'sp[_]%diagram%'
ORDER BY s.Name, p.Name
open cur
fetch next from cur into #schemaName,#procName
while ##fetch_status = 0
begin
if #procName <> 'DeleteAllProcedures'
exec('drop procedure ' + #schemaName + '.' + #procName)
fetch next from cur into #schemaName,#procName
end
close cur
deallocate cur
I think this is the simplest way:
DECLARE #sql VARCHAR(MAX)='';
SELECT #sql=#sql+'drop procedure ['+name +'];' FROM sys.objects
WHERE type = 'p' AND is_ms_shipped = 0
exec(#sql);
To get drop statements for all stored procedures in a database
SELECT 'DROP PROCEDURE' + ' '
+ F.NAME + ';'
FROM SYS.objects AS F where type='P'
DECLARE #sql VARCHAR(MAX)
SET #sql=''
SELECT #sql=#sql+'drop procedure ['+name +'];' FROM sys.objects
WHERE type = 'p' AND is_ms_shipped = 0
exec(#sql);
Try this, it work for me
DECLARE #spname sysname;
DECLARE SPCursor CURSOR FOR
SELECT SCHEMA_NAME(schema_id) + '.' + name
FROM sys.objects
WHERE type = 'P';
OPEN SPCursor;
FETCH NEXT FROM SPCursor INTO #spname;
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC('DROP PROCEDURE ' + #spname);
FETCH NEXT FROM SPCursor INTO #spname;
END
CLOSE SPCursor;
DEALLOCATE SPCursor;
DECLARE #DeleteProcCommand NVARCHAR(500)
DECLARE Syntax_Cursor CURSOR
FOR
SELECT 'DROP PROCEDURE ' + p.NAME
FROM sys.procedures p
OPEN Syntax_Cursor
FETCH NEXT FROM Syntax_Cursor
INTO #DeleteProcCommand
WHILE (##FETCH_STATUS = 0)
BEGIN
EXEC (#DeleteProcCommand)
FETCH NEXT FROM Syntax_Cursor
INTO #DeleteProcCommand
END
CLOSE Syntax_Cursor
DEALLOCATE Syntax_Cursor
Try this:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += N'DROP PROCEDURE dbo.'
+ QUOTENAME(name) + ';
' FROM sys.procedures
WHERE name LIKE N'spname%'
AND SCHEMA_NAME(schema_id) = N'dbo';
EXEC sp_executesql #sql;
ANSI compliant, without cursor
DECLARE #SQL national character varying(MAX)
SET #SQL= ''
SELECT #SQL= #SQL+ N'DROP PROCEDURE "' + REPLACE(SPECIFIC_SCHEMA, N'"', N'""') + N'"."' + REPLACE(SPECIFIC_NAME, N'"', N'""') + N'"; '
FROM INFORMATION_SCHEMA.ROUTINES
WHERE (1=1)
AND ROUTINE_TYPE = 'PROCEDURE'
AND ROUTINE_NAME NOT IN
(
'dt_adduserobject'
,'dt_droppropertiesbyid'
,'dt_dropuserobjectbyid'
,'dt_generateansiname'
,'dt_getobjwithprop'
,'dt_getobjwithprop_u'
,'dt_getpropertiesbyid'
,'dt_getpropertiesbyid_u'
,'dt_setpropertybyid'
,'dt_setpropertybyid_u'
,'dt_verstamp006'
,'dt_verstamp007'
,'sp_helpdiagrams'
,'sp_creatediagram'
,'sp_alterdiagram'
,'sp_renamediagram'
,'sp_dropdiagram'
,'sp_helpdiagramdefinition'
,'fn_diagramobjects'
,'sp_upgraddiagrams'
)
ORDER BY SPECIFIC_NAME
-- PRINT #SQL
EXEC(#SQL)
Without cursor, non-ansi compliant:
DECLARE #sql NVARCHAR(MAX) = N''
, #lineFeed NVARCHAR(2) = CHAR(13) + CHAR(10) ;
SELECT #sql = #sql + N'DROP PROCEDURE ' + QUOTENAME(SPECIFIC_SCHEMA) + N'.' + QUOTENAME(SPECIFIC_NAME) + N';' + #lineFeed
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_TYPE = 'PROCEDURE'
-- AND SPECIFIC_NAME LIKE 'sp[_]RPT[_]%'
AND ROUTINE_NAME NOT IN
(
SELECT name FROM sys.procedures WHERE is_ms_shipped <> 0
)
ORDER BY SPECIFIC_NAME
-- PRINT #sql
EXECUTE(#sql)
By mixing the cursor and system procedure, we would have a optimized solution, as follow:
DECLARE DelAllProcedures CURSOR
FOR
SELECT name AS procedure_name
FROM sys.procedures;
OPEN DelAllProcedures
DECLARE #ProcName VARCHAR(100)
FETCH NEXT
FROM DelAllProcedures
INTO #ProcName
WHILE ##FETCH_STATUS!=-1
BEGIN
DECLARE #command VARCHAR(100)
SET #command=''
SET #command=#command+'DROP PROCEDURE '+#ProcName
--DROP PROCEDURE #ProcName
EXECUTE (#command)
FETCH NEXT
FROM DelAllProcedures
INTO #ProcName
END
CLOSE DelAllProcedures
DEALLOCATE DelAllProcedures
ANSI compliant, without cursor
PRINT ('1.a. Delete stored procedures ' + CONVERT( VARCHAR(19), GETDATE(), 121));
GO
DECLARE #procedure NVARCHAR(max)
DECLARE #n CHAR(1)
SET #n = CHAR(10)
SELECT #procedure = isnull( #procedure + #n, '' ) +
'DROP PROCEDURE [' + schema_name(schema_id) + '].[' + name + ']'
FROM sys.procedures
EXEC sp_executesql #procedure
PRINT ('1.b. Stored procedures deleted ' + CONVERT( VARCHAR(19), GETDATE(), 121));
GO
Try this:
declare #procName varchar(500)
declare cur cursor
for SELECT 'DROP PROCEDURE [' + SCHEMA_NAME(p.schema_id) + '].[' + p.NAME + ']'
FROM sys.procedures p
open cur
fetch next from cur into #procName
while ##fetch_status = 0
begin
exec( #procName )
fetch next from cur into #procName
end
close cur
deallocate cur