Technical Article

Universal foreach aka sp_uforeach

,

Syntax:

EXEC master.dbo.sp_uforeach @table_name='table name or select statement',
@column_name='existing column name', 
@where_clause='where clause only when tabla name is used', 
@command='command with ? replace character', 
@replace_character='you can define a spec character which is used for replacing the 'looping object' (default '?')
@print_command_only= 1 only print the command, 0 execute it
@print_object_name= 1 print/select the object name the script working on (def 0)
@debug=1 enable debug information

There are two options how you can provide the ‘looping objects’ which the script in the @command parameter have to run on.

First you can use an existing table name in the @table_name parameter (for example ‘sys.databases’) and in this case you can also provide a where clause without the word ‘where’ in the @where_clause parameter to filter the objects (for example ‘state = 0′).

Second, you can use a select statement with a where clause, like ‘select name from sys.databases where status = 0′. Using select statement the parameter @where_clause is ignored, but the parameter @column_name have to be provided, because the records of this column will be used as ‘looping objects’.

If you only provide the @command parameter, the stored procedure works like sp_MSforeachdb:

/*DEFAULT(foreach_db without command):*/EXEC sp_uforeach @table_name='sys.databases', 
@column_name='name', 
@where_clause='', 
@command='', 
@print_command_only= 1

You can get help and examples using the @help=1 parameter as well.

/*============================================================================
  File:     sp_SQLApprentice_SQL2008_uforeach.sql

  Summary:  Flexible universal foreach, default behavior: sp_MSforeachdb

  Date:     2012.08
  
  Version:  v3.0 (with debug option)

  Tested:   SQL Server 2008 Version
------------------------------------------------------------------------------
  Written by Robert Virag
  Special thanks to Gábor Kiss
  Inspired by Aaron Bertrand's script (http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/)

  For more scripts and sample code, check out 
    http://www.SQLApprentice.net  
  
  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED 
  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  PARTICULAR PURPOSE.
============================================================================*/
USE master
go

if OBJECTPROPERTY(OBJECT_ID('sp_uforeach'), 'IsProcedure') = 1
drop procedure sp_uforeach
go

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE procedure [dbo].[sp_uforeach]
@table_nameNVARCHAR(MAX) = N'sys.databases',
@column_nameNVARCHAR(MAX) = N'name',
@where_clauseNVARCHAR(MAX) = N'',
@commandNVARCHAR(MAX) = N'',
@print_command_onlyBIT=1,
@print_object_name BIT = 0,
@replace_characterNCHAR(1)= N'?',
@help BIT = 0,
@use_quotename BIT = 0,
@debug BIT = 0

AS
BEGIN
SET NOCOUNT ON;

IF @help = 1
BEGIN
PRINT 'EXEC master.dbo.sp_uforeach @table_name=''table name or select statement'', '+ CHAR(13) + CHAR(10)
+ '@column_name=''existing column name'', '+ CHAR(13) + CHAR(10)
+ '@where_clause=''where clause can be used only when an existing table name is used'', '+ CHAR(13) + CHAR(10)
+ '@command=''command with ? replace character'', '+ CHAR(13) + CHAR(10)
+ '@replace_character=''you can define a spec character which is used for replacing the ''looping object'' (default ''?'')'+ CHAR(13) + CHAR(10)
+ '@print_command_only= 1 only print the command, 0 execute it'+ CHAR(13) + CHAR(10)
+ '@print_object_name= 1 print/select the object name the script working on (def 0)'+ CHAR(13) + CHAR(10)
+ '@debug=1 enable debug information' + CHAR(13) + CHAR(10)+ CHAR(13) + CHAR(10)
+ 'DEFAULT(foreach_db without command):'+ CHAR(13) + CHAR(10)
+ 'EXEC sp_uforeach @table_name=''sys.databases'', '+ CHAR(13) + CHAR(10)
+ '@column_name=''name'', '+ CHAR(13) + CHAR(10)
+ '@where_clause='''', '+ CHAR(13) + CHAR(10)
+ '@command='''', '+ CHAR(13) + CHAR(10)
+ '@print_command_only= 1' + CHAR(13) + CHAR(10)+ CHAR(13) + CHAR(10)
PRINT 'Usage Tipps: ' + CHAR(13) + CHAR(10)
+ '/*-------Configure the filtered jobs to write to the Windows Application event log*/'+ CHAR(13) + CHAR(10)
+ 'EXEC master.dbo.sp_uforeach @table_name=N''msdb.dbo.sysjobs'','+ CHAR(13) + CHAR(10)
+ '@column_name=N''job_id'','+ CHAR(13) + CHAR(10)
+ '@where_clause=N''name like ''''%TSI%'''''','+ CHAR(13) + CHAR(10)
+ '@command=N''USE [msdb] EXEC msdb.dbo.sp_update_job @job_id=N''''?'''', @notify_level_eventlog=2'','+ CHAR(13) + CHAR(10)
+ '@print_command_only =1 '+ CHAR(13) + CHAR(10)+ CHAR(13) + CHAR(10)
+ '/*-------Give sysadmin role to logins which name''s start with ''adm''*/' + CHAR(13) + CHAR(10)
+ 'EXEC master.dbo.sp_uforeach @table_name=''sys.server_principals'','+ CHAR(13) + CHAR(10)
+ '@column_name=''name'','+ CHAR(13) + CHAR(10)
+ '@where_clause=''type in (''''S'''',''''U'''') AND name LIKE ''''%adm_%'''''','+ CHAR(13) + CHAR(10)
+ '@command=''EXEC master..sp_addsrvrolemember @loginname=''''?'''', @rolename=N''''sysadmin'''''','+ CHAR(13) + CHAR(10)
+ '@print_command_only =1 '+ CHAR(13) + CHAR(10)+ CHAR(13) + CHAR(10)
+ '/*-------Index maintenance in a specific database*/'+ CHAR(13) + CHAR(10)
+ 'EXEC master.dbo.sp_uforeach @table_name=''USE AdventureWorks SELECT SCHEMA_NAME(schema_id)+''''.''''+name as name FROM AdventureWorks.sys.tables'','+ CHAR(13) + CHAR(10)
+ '@column_name=''name'',' + CHAR(13) + CHAR(10)
+ '@replace_character=''!'','+ CHAR(13) + CHAR(10)
+ '@print_command_only = 0,'+ CHAR(13) + CHAR(10) 
+ '@command=''USE AdventureWorks exec master.dbo.sp_uforeach @use_quotename=1,'+ CHAR(13) + CHAR(10)
+ '@print_command_only=1,'+ CHAR(13) + CHAR(10)
+ '@table_name=''''USE AdventureWorks SELECT a.index_id, name, avg_fragmentation_in_percent'+ CHAR(13) + CHAR(10)
+ 'FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID(''''''''!''''''''),NULL, NULL, NULL) AS a'+ CHAR(13) + CHAR(10)
+ 'JOIN sys.indexes AS b ON a.object_id = b.object_id AND a.index_id=b.index_id AND a.index_id>0'''','+ CHAR(13) + CHAR(10)
+ '@column_name=''''name'''','+ CHAR(13) + CHAR(10)
+ '@command=''''USE [AdventureWorks] ALTER INDEX ? ON ! REORGANIZE;'''''''+ CHAR(13) + CHAR(10)+ CHAR(13) + CHAR(10)

RETURN
END

DECLARE @sqlNVARCHAR(MAX)
DECLARE @objectNVARCHAR(300)
DECLARE @temp_table_name NVARCHAR(255)
DECLARE @table_name_old NVARCHAR(MAX)
DECLARE @inside_tmp_cmd NVARCHAR(MAX)
DECLARE @DBNAME NVARCHAR(255)

/*Creating temporary table from the select statement|START*/IF  @table_name LIKE '%SELECT %' OR @table_name LIKE '%select %'
BEGIN
SET @where_clause = N''
SET @temp_table_name = '[##' + CONVERT(NVARCHAR(255),NEWID()) + ']'

/*DEBUG START*/IF @debug=1
BEGIN
SET @table_name_old = @table_name
SET @table_name = 'SELECT @DBNAME_OUT=DB_NAME() ' + REPLACE(@table_name,'from', 'into ' + @temp_table_name +' from')
EXEC sp_executesql @table_name, @paramdefinition=N'@DBNAME_OUT NVARCHAR(255) OUTPUT',@DBNAME_OUT=@DBNAME OUTPUT
SELECT @DBNAME AS 'Creating the temporary table on this database from the ''select'' statement:'
SELECT @table_name_old AS 'Original query'
SELECT @table_name AS 'Inside query'
SELECT @temp_table_name AS 'Temp table'
SET @inside_tmp_cmd = 'select * from ' + @temp_table_name
EXEC sp_executesql @inside_tmp_cmd
END
ELSE
BEGIN
SET @table_name = REPLACE(@table_name,'from', 'into ' + @temp_table_name +' from')
EXEC sp_executesql @table_name
END
/*DEBUG END*/
SET @table_name = @temp_table_name
END
/*Creating temporary table from the selcet statement|END*/
CREATE TABLE #objects(myobject NVARCHAR(300));

SET @sql = N'SELECT ' 
+ @column_name 
+ ' FROM ' 
+ @table_name 
+ CASE WHEN @where_clause <> '' 
THEN ' WHERE ' + @where_clause 
ELSE ''
END

INSERT #objects EXEC sp_executesql @sql;


/*DEBUG START*/IF @debug=1
BEGIN
SET @inside_tmp_cmd = 'SELECT @DBNAME_OUT=DB_NAME()'
EXEC sp_executesql @inside_tmp_cmd, @paramdefinition=N'@DBNAME_OUT NVARCHAR(255) OUTPUT',@DBNAME_OUT=@DBNAME OUTPUT
SELECT @DBNAME AS 'Running the inside select on this database'
SELECT @sql AS 'Inside sql statement'
SELECT 'Content of the inside #object table' AS 'Info msg'
SELECT * from #objects
END
/*DEBUG END*/
DECLARE c CURSOR
LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR SELECT CASE WHEN @use_quotename = 1 
THEN
QUOTENAME(myobject)
ELSE 
myobject
END
FROM #objects;

OPEN c;

FETCH NEXT FROM c INTO @object;

WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql = REPLACE(@command, @replace_character, @object);

/*Print the executable commands plus aditional information|START*/IF @print_command_only = 1
BEGIN
PRINT '/* For table: ' + @table_name + ', colunm: ' + @column_name + ', value: ' + @object + ': */'
               + CHAR(13) + CHAR(10) + CHAR(13) + CHAR(10)
               + @sql 
               + CHAR(13) + CHAR(10) + CHAR(13) + CHAR(10);
END
/*Print the executable commands plus aditional information|END*/ELSE
BEGIN
/*Print the objects*/--PRINT @sql;
IF @print_object_name=1
BEGIN
SELECT @object
PRINT @object
END
/*Execute the command with the defined parameters*/EXEC sp_executesql @sql;
END

FETCH NEXT FROM c INTO @object;
END

CLOSE c;
DEALLOCATE c;
DROP TABLE #objects;
IF @table_name=@temp_table_name
BEGIN
SET @inside_tmp_cmd = 'DROP TABLE ' + @temp_table_name
EXEC sp_executesql @inside_tmp_cmd
END
END
GO

exec sys.sp_MS_marksystemobject 'sp_uforeach'
go

Rate

4 (1)

You rated this post out of 5. Change rating

Share

Share

Rate

4 (1)

You rated this post out of 5. Change rating