Cursor within a cursor for database dependent permissions - sql-server-2016

It would seem that my code should work, however testing indicates that all the results from the inner cursor are based on the current database at execution of the script and not dependent upon the USE statement within the script.
WHAT DID I FORGET?
DECLARE #Debug BIT = 1
DECLARE #newgrp VARCHAR(100) = 'ChangeTrakingViewableRole'
DECLARE #obj VARCHAR(100)
DECLARE #tsql VARCHAR(900)
DECLARE #tsql2 VARCHAR(900)
DECLARE #msg VARCHAR(900)
DECLARE #SchName VARCHAR(55)
DECLARE #TblName sysname
IF #Debug = 'TRUE' PRINT 'Debuging ON'
IF COALESCE(#newgrp,'') = ''
BEGIN
PRINT 'There was no DatabaseRole, User or Group Specified to take the place of the Public Role'
SET NOEXEC ON
END
ELSE
BEGIN
DECLARE DbCursor CURSOR FOR
SELECT 'USE '+DB_NAME(database_id) FROM sys.change_tracking_databases
OPEN DbCursor
FETCH NEXT FROM DbCursor INTO #obj
WHILE ##Fetch_Status = 0
BEGIN
SET #tsql2 = #obj+'; '
RAISERROR (#tsql2, 0, 1) WITH NOWAIT
EXEC sp_sqlexec #tsql2
-----------Commands within this next section are all database dependent
BEGIN --GRANT [VIEW CHANGE TRACKING] TO Change Tracking Enabled Tables
IF NOT EXISTS (SELECT name FROM sys.database_principals where name = #newgrp)
BEGIN
SET #tsql = N'CREATE ROLE '+#newgrp+' AUTHORIZATION [dbo]'
IF #Debug = 'TRUE'
BEGIN
SET #Msg = #tsql
RAISERROR (#Msg, 0, 1) WITH NOWAIT
END
ELSE
BEGIN
EXEC sp_sqlexec #tsql
END
END
DECLARE TblCursor CURSOR FOR
SELECT sch.name, tbl.name
FROM sys.change_tracking_tables chg
JOIN sys.tables tbl ON chg.object_id=tbl.object_id
JOIN sys.schemas sch ON tbl.schema_id=sch.schema_id
ORDER BY sch.name, tbl.name
OPEN TblCursor
FETCH NEXT FROM TblCursor INTO #SchName,#TblName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #tsql = 'GRANT VIEW CHANGE TRACKING ON ['+#SchName+'].['+#TblName+'] TO '+#newgrp
IF #Debug = 'TRUE'
BEGIN
SET #Msg = #tsql
RAISERROR (#Msg, 0, 1) WITH NOWAIT
END
ELSE
BEGIN
EXEC sp_sqlexec #tsql
END
FETCH NEXT FROM TblCursor INTO #SchName,#TblName
END
CLOSE TblCursor
DEALLOCATE TblCursor
END
FETCH NEXT FROM DbCursor INTO #obj
END
CLOSE DbCursor
DEALLOCATE DbCursor
END

The USE statements in your outer cursor are not doing anything for the statements generated aftwerward because they're being executed independently. When your outer cursor executes the USE it is just valid for the scope of the first EXEC sp_sqlexec call; it doesn't change the database context of the overall script. The context under which the rest of your script runs is still that of the overall script, meaning those statements will get run in the current database every time.
Bascially, you need to change this to generate a single script with the entirety of what you want to execute within the dynamic db context top to bottom, with the USE at the top, and then execute that whole thing in a single call to EXEC or sp_executesql.

Related

Delete multiple records from table through cursor in sql server

there are number of test IP's which I would like to remove through system defined sp
exec sp_delete_firewall_rule from sys.firewall_rules table in sql server
I am using below cursor but its not working
declare #name nvarchar(max)
declare cur CURSOR LOCAL for
select #name from sys.firewall_rules where [name] like '%TestIP%'
open cur
fetch next from cur into #name
while ##FETCH_STATUS = 0 BEGIN
exec sp_delete_firewall_rule #name
fetch next from cur into #name
END
close cur
deallocate cur
It worked for me, you just need to change a couple of things in your code.
In the select list include the table ColumnName [name] instead of variable. You did not pass any value to the variable so this gives a NULL result.
Include SP parameter while executing exec sp_delete_firewall_rule #name = #name1;
I have these IP’s in my firewall rules:
With the below code I am deleting the IP’s which has a name like TestIP1.
DECLARE #name1 nvarchar(128);
DECLARE MyCursor CURSOR FOR
SELECT [name] from sys.firewall_rules where [name] like '%TestIP1%';
OPEN MyCursor;
FETCH FROM MyCursor into #name1
WHILE ##FETCH_STATUS = 0 BEGIN
EXEC sp_delete_firewall_rule #name = #name1 ;
FETCH next from MyCursor into #name1
END
CLOSE MyCursor;
DEALLOCATE MyCursor;
GO
Now the result shows only 1 IP which is not included in the above delete list.

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

sp_OAMethod 'DeleteFile' Method not working in SQL Server 2008 R2

I have an sql stored procedure that I use to delete files from the windows file system using the sp_OAMethod. This used to work fine when we were using sql server 2005, however, it does not work at all now when using sql server 2008 R2. I have read that you can use SQLDMO/SQLCLR however, I cannot find any decent information regarding these methods. My previous code is below:
-- declare variables
declare #ObjectID nvarchar(10),
#ObjectType nvarchar(255),
#BackupName nvarchar(255),
#BackupLocation nvarchar(255),
#ExpiryDate datetime,
#DeletedStatus bit,
#SQL nvarchar(4000),
#SQL1 nvarchar(4000),
#SQL2 nvarchar(4000),
#Result int,
#FSO_Token int,
#FileLocation nvarchar(4000)
-- declare cursor for table backups
declare backupexpired_cursor cursor for
select dbo.tbl_BackupObjects.ObjectID, dbo.tbl_BackupObjects.ObjectType, dbo.tbl_BackupObjects.BackupName,
dbo.tbl_BackupObjects.BackupLocation, dbo.tbl_BackupObjects.ExpiryDate, dbo.tbl_BackupObjects.Deleted
from dbo.tbl_BackupObjects
where dbo.tbl_BackupObjects.Deleted <> 1
-- open cursor
open backupexpired_cursor
-- fetch the next record from the cursor
fetch next from backupexpired_cursor into #ObjectID, #ObjectType, #BackupName, #BackupLocation, #ExpiryDate, #DeletedStatus
while (##FETCH_STATUS <> -1)
begin
if (##FETCH_STATUS <> -2)
begin
if (#ExpiryDate < GetDate())
begin
if (#ObjectType = 'Table')
begin
begin try
begin transaction
-- Only done if the object type is a table object
-- Remove old backup
select #SQL = 'drop table dbo.' + quotename(#BackupName)
exec sp_executesql #SQL
-- update the deleted status and the date deleted of the deleted object
select #SQL1 = 'update tbl_BackupObjects
set Deleted = 1,
DeletedDate = GetDate()
where ObjectID = ''' + #ObjectID + ''''
exec sp_executesql #SQL1
commit transaction
end try
begin catch
rollback transaction
select #SQL1 = 'update tbl_BackupObjects
set Deleted = 0,
DeletedDate = NULL
where ObjectID = ''' + #ObjectID + ''''
exec sp_executesql #SQL1
end catch
end
else
begin
begin try
begin transaction
-- Only done if the object(view, stored procedure, and/or function is saved
-- in a file located on the windows file system.
-- Create File Location
set #FileLocation = 'G:\Backup Registry Script Files\' + #BackupLocation + '\' + #BackupName + ''
-- Create a token of the object
EXEC #Result = sp_OACreate 'Scripting.FileSystemObject', #FSO_Token OUTPUT
-- Call the deletefile method using the #FileLocation parameter and the token created above:
-- - The object token created by sp_OACreate
-- - The method name
-- - The method's return value
-- - Parameters that will be used by the object method
EXEC #Result = sp_OAMethod #FSO_Token, 'DeleteFile', NULL, #FileLocation
-- Execute ole method
EXEC #Result = sp_OADestroy #FSO_Token
-- update the deleted status and the date deleted of the deleted object
select #SQL1 = 'update tbl_BackupObjects
set Deleted = 1,
DeletedDate = GetDate()
where ObjectID = ''' + #ObjectID + ''''
exec sp_executesql #SQL1
commit transaction
end try
begin catch
rollback transaction
select #SQL1 = 'update tbl_BackupObjects
set Deleted = 0,
DeletedDate = GetDate()
where ObjectID = ''' + #ObjectID + ''''
exec sp_executesql #SQL1
end catch
end
end
end
-- fetch the next record from the cursor
fetch next from backupexpired_cursor into #ObjectID, #ObjectType, #BackupName, #BackupLocation, #ExpiryDate, #DeletedStatus
end
-- set the Last and Next Removal Dates
select #SQL2 = 'update tbl_BackupRemovalDate
set LastRemovalDate = GetDate(),
NextRemovalDate = GetDate() + 7'
exec sp_executesql #SQL2
-- close cursor
close backupexpired_cursor
deallocate backupexpired_cursor
I have seen that SQLDMO is quite similar to what I have, however I cannot find any information on how to delete a file system file using this method. Can anyone help?
Do you have Enable Ole Automation Procedures feature ?
try this
EXEC sp_configure 'show advanced options', 1
RECONFIGURE
EXEC sp_configure 'Ole Automation Procedures', 1
RECONFIGURE

EXEC within the TRY...CATCH block

I am trying to drop all the tables in the database with the following script:
WHILE EXISTS(SELECT * FROM sys.tables where is_ms_shipped = 0)
BEGIN
EXEC sp_MSforeachtable 'DROP TABLE ?'
END
I am getting lots of errors because of the foreign key constraints. But that is fine, tables are dropped anyway. I would like to get rid of the error messages with the following script.
WHILE EXISTS(SELECT * FROM sys.tables where is_ms_shipped = 0)
BEGIN
BEGIN TRY
EXEC sp_MSforeachtable 'DROP TABLE ?';
END TRY
BEGIN CATCH
END CATCH
END
This script just runs forever trying to drop the first table.
What am I doing wrong?
I'm not sure what's wrong with your query, but I've dropped tables using a cursor like this:
DECLARE #name as varchar(100)
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT name FROM sys.tables
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
execute('drop table ' +#name)
FETCH NEXT FROM MyCursor INTO #name
END
CLOSE MyCursor
DEALLOCATE MyCursor

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