Need to declare a cursor with a variable in the declaration - sql

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

Related

Update specific column in all tables base on specific value

I'm trying to update all table based on my value. This is a script Task in my SSIS package.
But I get the error:
Multiple-step OLE DB operation generated errors. Check each OLE DB status value, if available. No work was done.
Code looks like this:
DECLARE #Column_name varchar(MAX)
DECLARE #Column_Datatype varchar(max)
SET #COLUMN_NAME='site_id'
SET #COLUMN_DATATYPE='varchar(10)'
------------------------------------------------Code---------------------------------------------------
GO
--Declare Variables
DECLARE #TableName VARCHAR(100)
DECLARE #TableSchema VARCHAR(100)
DECLARE #COLUMN_NAME VARCHAR(50)
DECLARE #COLUMN_NAME_UPDATE VARCHAR(50)
SET #COLUMN_NAME_UPDATE = ?
SET #COLUMN_NAME='site_id'
DECLARE #COLUMN_DATATYPE VARCHAR(50)
SET #COLUMN_DATATYPE='varchar(max)'
--Declare Cursor
DECLARE CUR CURSOR FOR
SELECT TABLE_SCHEMA,
TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
--OPEN CURSOR
OPEN CUR
--Fetch First Row
FETCH NEXT FROM CUR INTO #TableSchema,#TableName
--Loop
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #SQL NVARCHAR(MAX)
SET #SQL= ( SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#TableName AND COLUMN_NAME=#COLUMN_NAME
and Table_Schema=#TableSchema)
BEGIN
SET #SQL='UPDATE '+ #TableSchema+'.'+ '[' + #TableName + ']' + ' SET '+#COLUMN_NAME + '='
+ '''' + #COLUMN_NAME_UPDATE + '''' + 'WHERE site_id IS NULL '
PRINT #SQL
EXEC ( #SQL)
END
FETCH NEXT FROM CUR INTO #TableSchema,#TableName
END
--Close and Deallocate Cursor
CLOSE CUR
DEALLOCATE CUR
I use a variable like input.
What's wrong?

Fill Factor--SQL Server, Is there a way to set Fill Factor across a database, server, or schema?

I understand that you can set a default fill factor but I want to change existing fill factors back to default across an entire server.
DECLARE #Database VARCHAR(255)
DECLARE #Table VARCHAR(255)
DECLARE #cmd NVARCHAR(500)
DECLARE #fillfactor INT
SET #fillfactor = 90
DECLARE DatabaseCursor CURSOR FOR
SELECT name FROM master.dbo.sysdatabases
WHERE name NOT IN ('master','msdb','tempdb','model','distribution')
ORDER BY 1
OPEN DatabaseCursor
FETCH NEXT FROM DatabaseCursor INTO #Database
WHILE ##FETCH_STATUS = 0
BEGIN
SET #cmd = 'DECLARE TableCursor CURSOR FOR SELECT ''['' + table_catalog + ''].['' + table_schema + ''].['' +
table_name + '']'' as tableName FROM [' + #Database + '].INFORMATION_SCHEMA.TABLES
WHERE table_type = ''BASE TABLE'''
-- create table cursor
EXEC (#cmd)
OPEN TableCursor
FETCH NEXT FROM TableCursor INTO #Table
WHILE ##FETCH_STATUS = 0
BEGIN
IF (##MICROSOFTVERSION / POWER(2, 24) >= 9)
BEGIN
-- SQL 2005 or higher command
SET #cmd = 'ALTER INDEX ALL ON ' + #Table + ' REBUILD WITH (FILLFACTOR = ' + CONVERT(VARCHAR(3),#fillfactor) + ')'
EXEC (#cmd)
END
ELSE
BEGIN
-- SQL 2000 command
DBCC DBREINDEX(#Table,' ',#fillfactor)
END
FETCH NEXT FROM TableCursor INTO #Table
END
CLOSE TableCursor
DEALLOCATE TableCursor
FETCH NEXT FROM DatabaseCursor INTO #Database
END
CLOSE DatabaseCursor
DEALLOCATE DatabaseCursor
BEGIN TRAN
DECLARE #table_name VARCHAR(MAX)
DECLARE table_cursor CURSOR LOCAL
FOR
SELECT
TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
OPEN table_cursor
FETCH NEXT FROM table_cursor INTO
#table_name
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'ALTER INDEX ALL ON '+#table_name+'
REBUILD WITH (FILLFACTOR = 100)'
END
CLOSE table_cursor;
DEALLOCATE table_cursor;
ROLLBACK TRAN

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.

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

Using cursor within UDF, returns a column which has name of different table

Create FUNCTION [dbo].[fn_GetStockDeatils]()
RETURNS #Results TABLE
(
PurchaseData nvarchar(50) NOT NULL
)
AS
BEGIN
Declare #tableName varchar(25)
Declare #PKKey numeric(18,0)
DECLARE StockCursor CURSOR FOR Select tableName ,PKKey from INVM
OPEN StockCursor
FETCH StockCursor INTO #tableName ,#PKKey
Begin
Insert #Results Select PurchaseData from #tableName.PKKey =#PKKey
END
FETCH StockCursor INTO #tableName ,#PKKey
close StockCursor
DEALLOCATE StockCursor
Return
END
I have already written this but it does not working properly. #tableName contains the name of the table. Please help me if possible.
Sounds like you need an dynamic SQL commands, but you cannot use dynamic SQL from a function. For that you have to create stored procedure.
CREATE PROCEDURE [dbo].[GetStockDeatils]
AS
BEGIN
DECLARE #tableName varchar(25),
#PKKey numeric(18,0),
#dsql nvarchar(max) = N''
IF OBJECT_ID('tempdb.dbo.#Results') IS NOT NULL DROP TABLE dbo.#Results
CREATE TABLE dbo.#Results(PurchaseData nvarchar(50) NOT NULL)
DECLARE StockCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT tableName ,PKKey
FROM INVM
OPEN StockCursor
FETCH NEXT FROM StockCursor INTO #tableName, #PKKey
WHILE ##FETCH_STATUS = 0
BEGIN
SET #dsql = 'Insert dbo.#Results Select PurchaseData from ' + #tableName + ' WHERE PKKey = ' + CAST(#PKKey AS varchar(10))
EXEC sp_executesql #dsql
FETCH NEXT FROM StockCursor INTO #tableName, #PKKey
END
CLOSE StockCursor
DEALLOCATE StockCursor
SELECT *
FROM dbo.#Results
END
EXEC [dbo].[GetStockDeatils]
Demo on SQLFiddle
Procedure creating view
CREATE PROCEDURE [dbo].[GetStockDeatils]
AS
BEGIN
DECLARE #dsql nvarchar(max) = N''
IF OBJECT_ID('dbo.v_Results') IS NOT NULL DROP VIEW dbo.v_Results
SELECT #dsql +=
'UNION ALL SELECT PurchaseData FROM ' + tableName +
' WHERE PKKey = ' + CAST(PKKey AS varchar(10))
FROM dbo.INVM
SET #dsql = N'CREATE VIEW dbo.v_Results AS ' + STUFF(#dsql, 1, 10, '')
EXEC sp_executesql #dsql
END
EXEC [dbo].[GetStockDeatils]
SELECT *
FROM dbo.v_Results