In SQL Server 6.x, Microsoft introduced SP_MSFOREACHTABLE
and SP_MSFOREACHDB. These powerful stored procedures allow a DBA to loop
through each table or database and perform an action on the object. Although
these are powerful stored procedures, it is disappointing that they have not
been extended into other objects. This article will show you how to fully
extend this stored procedure to work in other areas of SQL Server.
First let’s talk briefly about how these stored procedures work
today and then I’ll get into how to make them do what we want them to do to
other objects. The two stored replace places where you would have to write a
complex cursor as shown below with a single line of code:
--Old way
DECLARE @dataname varchar(255),
@dataname_header varchar(255)
DECLARE datanames_cursor CURSOR FOR SELECT name FROM master..sysdatabases
WHERE name not in ('master', 'pubs', 'tempdb',
'model')
OPEN datanames_cursor
FETCH NEXT FROM datanames_cursor INTO @dataname
IF (@@fetch_status = 0)
BEGIN
SELECT @dataname_header = "Database " +
RTRIM(UPPER(@dataname))
PRINT @dataname_header
SELECT @dataname_header = RTRIM(UPPER(@dataname))
EXEC ("DBCC CHECKDB " + "(" + @dataname
+ ")")
END
CLOSE datanames_cursor
DEALLOCATE datanames_cursor
The new way to do replace the above syntax is to run the
following line of code:
--New way
sp_MSforeachtable @command1="print '?' dbcc checktable ('?')"
The above syntax will run DBCC CHECKTABLE, which performs
consistency checks against every table. Although behind the scenes, a cursor is
still being run, at least you won’t have to worry about it or its syntax. To
execute the stored procedures as they are now, you must only pass in a single
parameter, @command1. So if you wished to loop through every table and
count the number of records, you could use the following syntax:
sp_msforeachtable "Print '?'
select count(*) from ?"
As you can see, the question mark (?) represents the table
name in this case. Here are some more advanced parameters that can be used:
|
Parameter
|
Description
|
|
@command1
|
First command to execute against every object
|
|
@replacechar
|
What placeholder will be used to represent an object. By default, this is ‘?’.
|
|
@command2
|
Second command to execute against every object
|
|
@command3
|
Third command to execute against every object
|
|
@whereand
|
WHERE clause applied to narrow down the list of objects.
|
|
@precommand
|
Command run a single time before the loop.
|
|
@postcommand
|
Command run a single time after the loop.
|
So, to fully use some of the above syntax, here’s an example
that will capture all tables that begin with the word categories:
sp_MSforeachtable @command1 =
"Print '?'",
@command2 = "select count(*) from ?",
@precommand = "Print 'Listing of all
tables counts at ' select getdate()",
@postcommand = "Print 'Complete!'",
@whereand = "and name like 'categories%'
order by name desc"
Would output the following results:
Listing of all tables counts
at
------------------------------------------------------
2002-06-14 12:21:54.193
[dbo].[Categories2]
-----------
0
[dbo].[Categories]
-----------
92
Complete!
Now that we have the basics out of the way, let’s discuss
how to make your own SP_MSFOREACH stored procedure. Each of the SP_MSFORECH
stored procedures reference another sproc called
SP_MSFOREACHWORKER, which generically accepts an array of data and loops
through it. Creating a customized stored procedure can be easily created by
modifying a piece of the query. Here is the part of a SP_MS_FOREACH query that
you would care about.
/* Create the select */
exec(N'declare hCForEach cursor global
for select ''['' + REPLACE(user_name(uid), N'']'', N'']]'') + '']'' + ''.'' + ''[''
+ REPLACE(object_name(id), N'']'', N'']]'') + '']''
from dbo.sysobjects o '
+ N' where
OBJECTPROPERTY(o.id, N''IsUserTable'') = 1 ' + N' and o.category
& ' + @mscat + N' = 0 '
+ @whereand)
declare @retval int
select @retval = @@error
if (@retval = 0)
exec @retval = sp_MSforeach_worker @command1, @replacechar,
@command2, @command3
To make a SP_MSFOREACHVIEW stored procedure, all you would
have to do is change the bolded N''IsUserTable''
to N''IsView'' or for a trigger you could use the IsTrigger syntax. The final cut of code would
look like the below:
exec(N'declare hCForEach cursor global
for select ''['' + REPLACE(user_name(uid), N'']'', N'']]'') + '']'' + ''.'' + ''[''
+ REPLACE(object_name(id), N'']'', N'']]'') + '']''
from dbo.sysobjects o '
+ N' where
OBJECTPROPERTY(o.id, N''IsView'') = 1 ' + N' and o.category
& ' + @mscat + N' = 0 '
+ @whereand)
declare @retval int
select @retval = @@error
if (@retval = 0)
exec @retval = sp_MSforeach_worker @command1, @replacechar,
@command2, @command3
Here are the links to the full script in the
SQLServerCentral.com script library to get you started so you won’t have to be
bothered to create some of the basics:
The chunk of code that I included above can be modified
further to perform additional actions. If you come up with a neat way to extend
this sproc, please post the in the script library and
share them with
the community!