sp_MSforeachtable 与 sp_MSforeachdb


在MSSQL里有许多不公开的系统存储过程,其中可能常用的sp_MSforeachtable和sp_MSforeachdb有这2个。
分别用于遍历某数据库的每个用户表、每个数据库。

sp_MSforeachtable


create   proc  sp_MSforeachtable
    
@command1   nvarchar ( 2000 ),             --第一条运行的T-SQL
    
@replacechar   nchar ( 1 =  N ' ? ' ,          --指定的占位符
    
@command2   nvarchar ( 2000 =   null ,      --第二条运行的T-SQL   
   
@command3   nvarchar ( 2000 =   null ,      --第三条运行的T-SQL
    
@whereand   nvarchar ( 2000 =   null ,      --表的选择条件
    
@precommand   nvarchar ( 2000 =   null ,    --在表前执行的指令
    
@postcommand   nvarchar ( 2000 =   null    --在表后执行的指令
as
    
/*  This proc returns one or more rows for each table (optionally, matching @where), with each table defaulting to its own result set  */
    
/*  @precommand and @postcommand may be used to force a single result set via a temp table.  */
    
/*  Preprocessor won't replace within quotes so have to use str().  */
    
declare   @mscat   nvarchar ( 12 )
    
select   @mscat   =   ltrim ( str ( convert ( int 0x0002 )))

    
if  ( @precommand   is   not   null )
        
exec ( @precommand )

    
/*  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 )
    --上面的代码,就是定义游标根据系统表sysobjects获取用户表,@whereand就确定 sysobjects的where条件
    
declare   @retval   int
    
select   @retval   =   @@error
    
if  ( @retval   =   0 )
        --调用sp_MSforeach_worker 执行游标遍历
        --sp_MSforeach_worker存储过程见最后
        
exec   @retval   =  sp_MSforeach_worker  @command1 @replacechar @command2 @command3

    
if  ( @retval   =   0   and   @postcommand   is   not   null )
        
exec ( @postcommand )

    
return   @retval

go
--统计数据库里每个表的详细情况
exec sp_MSforeachtable 'sp_spaceused ''?'''
--获得每个表的记录数和容量
exec sp_MSforeachtable 'select ''?''','?', 'sp_spaceused ''?''', 'SELECT count(*) FROM ? '
--获得所有的数据库的存储空间
exec sp_MSforeachdb  'select  ''?''','?','sp_spaceused '
--检查所有的数据库
exec sp_MSforeachdb  @command1="print '?'",'DBCC CHECKDB (?) '
--更新PUBS数据库中已t开头的所有表的统计
exec sp_MSforeachtable
       'print ''*'' update statistics * ',
       '*',
       null,
       null,
       ' and name like ''t%''',
       'print ''Updating Statistics.....''',
       'print ''Complete Update Statistics!'''

--删除当前数据库所有表中的数据
sp_MSforeachtable 'Delete from ?'
sp_MSforeachtable 'Truncate Table ?'

--更新Table1/Table2中note列为NULL的值
sp_MSforeachtable 'Update ? Set note='''' Where note is null',null,null,null,' AND o.name in (''Table1'',''Table2'')



sp_MSforeachdb 在应用上与sp_MSforeachTable一样,只是没有@whereand 这个条件变量而已
/*
 * The following table definition will be created by SQLDMO at start of each connection.
 * We don't create it here temporarily because we need it in Exec() or upgrade won't work.
 
*/
Create   proc  sp_MSforeachdb
    
@command1   nvarchar ( 2000 ), 
    
@replacechar   nchar ( 1 =  N ' ? '
    
@command2   nvarchar ( 2000 =   null
    
@command3   nvarchar ( 2000 =   null ,
    
@precommand   nvarchar ( 2000 =   null
    
@postcommand   nvarchar ( 2000 =   null
as
   
set  deadlock_priority low
    
/*  This proc returns one or more rows for each accessible db, with each db defaulting to its own result set  */
    
/*  @precommand and @postcommand may be used to force a single result set via a temp table.  */
    
/*  Preprocessor won't replace within quotes so have to use str().  */
    
declare   @inaccessible   nvarchar ( 12 ),  @invalidlogin   nvarchar ( 12 ),  @dbinaccessible   nvarchar ( 12 )
    
select   @inaccessible   =   ltrim ( str ( convert ( int 0x03e0 ),  11 ))
    
select   @invalidlogin   =   ltrim ( str ( convert ( int 0x40000000 ),  11 ))
    
select   @dbinaccessible   =  N ' 0x80000000 '          /*  SQLDMODbUserProf_InaccessibleDb; the negative number doesn't work in convert()  */

    
if  ( @precommand   is   not   null )         exec ( @precommand )

    
declare   @origdb   nvarchar ( 128 )
    
select   @origdb   =   db_name ()

    
/*  If it's a single user db and there's an entry for it in sysprocesses who isn't us, we can't use it.  */
    
/*  Create the select  */
    
exec (N ' declare hCForEach cursor global for select name from master.dbo.sysdatabases d  '   +
            N
'  where (d.status &  '   +   @inaccessible   +  N '  = 0) '   +
            N
'  and ((DATABASEPROPERTY(d.name,  '' issingleuser '' ) = 0 and (has_dbaccess(d.name) = 1)) or  '   +
            N
'  ( DATABASEPROPERTY(d.name,  '' issingleuser '' ) = 1 and not exists  '   +
            N
'  (select * from master.dbo.sysprocesses p where dbid = d.dbid and p.spid <> @@spid))) '  )

    
declare   @retval   int
    
select   @retval   =   @@error
    
if  ( @retval   =   0 )
        
exec   @retval   =  sp_MSforeach_worker  @command1 @replacechar @command2 @command3

    
if  ( @retval   =   0   and   @postcommand   is   not   null )
        
exec ( @postcommand )

    
declare   @tempdb   nvarchar ( 258 )
       
SELECT   @tempdb   =   REPLACE ( @origdb , N ' ] ' , N ' ]] ' )
       
exec  (N ' use  '   +  N ' [ '   +   @tempdb   +  N ' ] ' )

    
return   @retval

go


sp_MSforeach_worker
/*
 * This is the worker proc for all of the "for each" type procs.  Its function is to read the
 * next replacement name from the cursor (which returns only a single name), plug it into the
 * replacement locations for the commands, and execute them.  It assumes the cursor "hCForEach"
 * has already been opened by its caller.
 
*/
create   proc  sp_MSforeach_worker
    
@command1   nvarchar ( 2000 ), 
    
@replacechar   nchar ( 1 =  N ' ? '
    
@command2   nvarchar ( 2000 =   null
    
@command3   nvarchar ( 2000 =   null
as

    
create   table  #qtemp (     /*  Temp command storage  */
        qnum                
int                  NOT   NULL ,
        qchar                
nvarchar ( 2000 )    COLLATE database_default  NULL
    )

    
set  nocount  on
    
declare   @name   nvarchar ( 517 ),  @namelen   int @q1   nvarchar ( 2000 ),  @q2   nvarchar ( 2000 )
    
declare   @q3   nvarchar ( 2000 ),  @q4   nvarchar ( 2000 ),  @q5   nvarchar ( 2000 )
    
declare   @q6   nvarchar ( 2000 ),  @q7   nvarchar ( 2000 ),  @q8   nvarchar ( 2000 ),  @q9   nvarchar ( 2000 ),  @q10   nvarchar ( 2000 )
    
declare   @cmd   nvarchar ( 2000 ),  @replacecharindex   int @useq   tinyint @usecmd   tinyint @nextcmd   nvarchar ( 2000 )
   
declare   @namesave   nvarchar ( 517 ),  @nametmp   nvarchar ( 517 ),  @nametmp2   nvarchar ( 258 )

    
open  hCForEach
    
fetch  hCForEach  into   @name
    
/*  Loop for each database  */
    
while  ( @@fetch_status   >=   0
    
begin
        
/*  Initialize.  */
        
/*  save the original dbname  */
       
select   @namesave   =   @name
        
select   @useq   =   1 @usecmd   =   1 @cmd   =   @command1 @namelen   =   datalength ( @name )
        
while  ( @cmd   is   not   null )
        
begin          /*  Generate @q* for exec()  */
            
/*
             * Parse each @commandX into a single executable batch.
             * Because the expanded form of a @commandX may be > OSQL_MAXCOLLEN_SET, we'll need to allow overflow.
             * We also may append @commandX's (signified by '++' as first letters of next @command).
             
*/
            
select   @replacecharindex   =   charindex ( @replacechar @cmd )
            
while  ( @replacecharindex   <>   0
            
begin

                    
/*  7.0, if name contains ' character, and the name has been single quoted in command, double all of them in dbname  */
                    
/*  if the name has not been single quoted in command, do not doulbe them  */
                    
/*  if name contains ] character, and the name has been [] quoted in command, double all of ] in dbname  */
                    
select   @name   =   @namesave
                    
select   @namelen   =   datalength ( @name )
                    
declare   @tempindex   int
                    
if  ( substring ( @cmd @replacecharindex   -   1 1 =  N ''''
                    
begin
                       
/*  if ? is inside of '', we need to double all the ' in name  */
                       
select   @name   =   REPLACE ( @name , N '''' , N '''''' )
                    
end  
                   
else   if  ( substring ( @cmd @replacecharindex   -   1 1 =  N ' [ '
               
    begin
                       
/*  if ? is inside of [], we need to double all the ] in name  */
                       
select   @name   =   REPLACE ( @name , N ' ] ' , N ' ]] ' )
                    
end  
                   
else   if  (( @name   LIKE  N ' %].%] ' and  ( substring ( @name 1 1 =  N ' [ ' )) 
                   
begin
                       
/*  ? is NOT inside of [] nor '', and the name is in [owner].[name] format, handle it  */
                       
/*  !!! work around, when using LIKE to find string pattern, can't use '[', since LIKE operator is treating '[' as a wide char  */
                       
select   @tempindex   =   charindex (N ' ].[ ' @name )
                       
select   @nametmp    =   substring ( @name 2 @tempindex - 2  )
                       
select   @nametmp2   =   substring ( @name @tempindex + 3 len ( @name ) - @tempindex - 3  )
                       
select   @nametmp    =   REPLACE ( @nametmp , N ' ] ' , N ' ]] ' )
                       
select   @nametmp2   =   REPLACE ( @nametmp2 , N ' ] ' , N ' ]] ' )
                       
select   @name   =  N ' [ '   +   @nametmp   +  N ' ].[ '   +   @nametmp2   +   ' ] '
                    
end  
                   
else   if  (( @name   LIKE  N ' %] ' and  ( substring ( @name 1 1 =  N ' [ ' )) 
                   
begin
                       
/*  ? is NOT inside of [] nor '', and the name is in [name] format, handle it  */
                       
/*  j.i.c., since we should not fall into this case  */
                       
/*  !!! work around, when using LIKE to find string pattern, can't use '[', since LIKE operator is treating '[' as a wide char  */
                        
select   @nametmp   =   substring ( @name 2 len ( @name ) - 2  )
                        
select   @nametmp   =   REPLACE ( @nametmp , N ' ] ' , N ' ]] ' )
                        
select   @name   =  N ' [ '   +   @nametmp   +  N ' ] '
                    
end
                    
/*  Get the new length  */
                    
select   @namelen   =   datalength ( @name )
                    
/*  start normal process  */
                   
if  ( datalength ( @cmd +   @namelen   -   1   >   2000
                   
begin
               
    /*  Overflow; put preceding stuff into the temp table  */
                       
if  ( @useq   >   9
                       
begin
                           
raiserror   55555  N ' sp_MSforeach_worker assert failed:  command too long '
                           
close  hCForEach deallocate  hCForEach
                           
return   1
                       
end
               
        if  ( @replacecharindex   <   @namelen begin
                       
/*  If this happened close to beginning, make sure expansion has enough room.  */
                       
/*  In this case no trailing space can occur as the row ends with @name.  */
                       
select   @nextcmd   =   substring ( @cmd 1 @replacecharindex )
                       
select   @cmd   =   substring ( @cmd @replacecharindex   +   1 2000 )
                       
select   @nextcmd   =   stuff ( @nextcmd @replacecharindex 1 @name )
                   
    select   @replacecharindex   =   charindex ( @replacechar @cmd )
                       
insert  #qtemp  values  ( @useq @nextcmd )
                       
select   @useq   =   @useq   +   1
                       
continue
                   
end
               
    /*  Move the string down and stuff() in-place.  */
                   
/*  Because varchar columns trim trailing spaces, we may need to prepend one to the following string.  */
                   
/*  In this case, the char to be replaced is moved over by one.  */
                   
insert  #qtemp  values  ( @useq substring ( @cmd 1 @replacecharindex   -   1 ))
                   
if  ( substring ( @cmd @replacecharindex   -   1 1 =  N '   '
                   
begin
                       
select   @cmd   =  N '   '   +   substring ( @cmd @replacecharindex 2000 )
                   
    select   @replacecharindex   =   2
                   
end  
                   
else  
                   
begin
                       
select   @cmd   =   substring ( @cmd @replacecharindex 2000 )
                       
select   @replacecharindex   =   1
                   
end
                   
select   @useq   =   @useq   +   1
               
end
           
    select   @cmd   =   stuff ( @cmd @replacecharindex 1 @name )
               
select   @replacecharindex   =   charindex ( @replacechar @cmd )
           
end
       
    /*  Done replacing for current @cmd.  Get the next one and see if it's to be appended.  */
           
select   @usecmd   =   @usecmd   +   1
           
select   @nextcmd   =   case  ( @usecmd when   2   then   @command2   when   3   then   @command3   else   null   end
       
    if  ( @nextcmd   is   not   null   and   substring ( @nextcmd 1 2 =  N ' ++ '
           
begin
               
insert  #qtemp  values  ( @useq @cmd )
               
select   @cmd   =   substring ( @nextcmd 3 2000 ),  @useq   =   @useq   +   1
           
    continue
           
end
           
/*  Now exec() the generated @q*, and see if we had more commands to exec().  Continue even if errors.  */
       
    /*  Null them first as the no-result-set case won't.  */
          
select   @q1   =   null @q2   =   null @q3   =   null @q4   =   null @q5   =   null @q6   =   null @q7   =   null @q8   =   null @q9   =   null @q10   =   null
           
select   @q1   =  qchar  from  #qtemp  where  qnum  =   1
       
    select   @q2   =  qchar  from  #qtemp  where  qnum  =   2
           
select   @q3   =  qchar  from  #qtemp  where  qnum  =   3
           
select   @q4   =  qchar  from  #qtemp  where  qnum  =   4
       
    select   @q5   =  qchar  from  #qtemp  where  qnum  =   5
           
select   @q6   =  qchar  from  #qtemp  where  qnum  =   6
           
select   @q7   =  qchar  from  #qtemp  where  qnum  =   7
       
    select   @q8   =  qchar  from  #qtemp  where  qnum  =   8
           
select   @q9   =  qchar  from  #qtemp  where  qnum  =   9
           
select   @q10   =  qchar  from  #qtemp  where  qnum  =   10
       
    truncate   table  #qtemp
           
exec  ( @q1   +   @q2   +   @q3   +   @q4   +   @q5   +   @q6   +   @q7   +   @q8   +   @q9   +   @q10   +   @cmd )
           
select   @cmd   =   @nextcmd @useq   =   1
       
end  
   
    /*  while @cmd is not null, generating @q* for exec()  */
       
/*  All commands done for this name.  Go to next one.  */
       
fetch  hCForEach  into   @name
    end
    /*
 while FETCH_SUCCESS  */
    close  hCForEach deallocate  hCForEach
    return   0


其他一些sql中的扩展存储的总结:

xp_availablemedia    (无)      显示系统上可用的盘符'C:\' xp_availablemedia 
xp_enumgroups                     列出当前系统的使用群组及其说明 xp_enumgroups 
xp_enumdsn            (无)      列出系统上已经设置好的ODBC数据源名称 xp_enumdsn 
xp_dirtree            (无)      显示某个目录下的子目录与文件架构 xp_dirtree 'C:\inetpub\wwwroot\' 
xp_getfiledetails    (无)      获取某文件的相关属性 xp_getfiledetails 'C:\inetpub\wwwroot.asp' 
dbp.xp_makecab        (无)     将目标计算机多个档案压缩到某个档案里所压缩的档案都可以接在参数的后面用豆号隔开             dbp.xp_makecab 'C:\lin.cab','evil',1,'C:\inetpub\mdb.asp' 
xp_unpackcab           (无)      解压缩 xp_unpackcab 'C:\hackway.cab','C:\temp',1 
xp_ntsec_enumdomains  (无)      列出服务器域名 xp_ntsec_enumdomains 
xp_servicecontrol     (无)      停止或者启动某个服务 xp_servicecontrol 'stop','schedule' 
xp_terminate_process  (无)     用pid来停止某个执行中的程序 xp_terminate_process 123 
dbo.xp_subdirs        (无)      只列某个目录下的子目录 dbo.xp_subdirs 'C:\'


网络上有个存储过程 sp_MSforeachObject,这是根据sp_MSforeachtable延伸出来的一个存储过程,扩展了应用。个人感觉很有用,这里推荐下。
USE  MASTER
GO
-- =============================================================
--
@objectType 对象类型 
--
1  IsUserTable
--
2  IsView
--
3  IsTrigger
--
4  IsProcedure
--
5  IsDefault
--
6  IsForeignKey
--
7  IsScalarFunction
--
8  IsInlineFunction
--
9  IsPrimaryKey
--
10 IsExtendedProc
--
11 IsReplProc
--
12 IsRule
--
=============================================================
Create   proc  sp_MSforeachObject
     
@objectType   int = 1 ,
     
@command1   nvarchar ( 2000 ),
     
@replacechar   nchar ( 1 =  N ' ? ' ,
     
@command2   nvarchar ( 2000 =   null ,
     
@command3   nvarchar ( 2000 =   null ,
     
@whereand   nvarchar ( 2000 =   null ,
     
@precommand   nvarchar ( 2000 =   null ,
     
@postcommand   nvarchar ( 2000 =   null
as
/*  This proc returns one or more rows for each table (optionally, matching @where), with each table defaulting to its
own result set 
*/
/*  @precommand and @postcommand may be used to force a single result set via a temp table.  */
/*  Preprocessor won't replace within quotes so have to use str().  */
declare   @mscat   nvarchar ( 12 )
select   @mscat   =   ltrim ( str ( convert ( int 0x0002 )))
if  ( @precommand   is   not   null )
exec ( @precommand )
/*  Defined @isobject for save object type  */
Declare   @isobject   varchar ( 256 )
select   @isobject =   case   @objectType   when   1   then   ' IsUserTable '
     
when   2   then   ' IsView '
     
when   3   then   ' IsTrigger '
     
when   4   then   ' IsProcedure '
     
when   5   then   ' IsDefault '
     
when   6   then   ' IsForeignKey '
     
when   7   then   ' IsScalarFunction '
     
when   8   then   ' IsInlineFunction '
     
when   9   then   ' IsPrimaryKey '
     
when   10   then   ' IsExtendedProc '
     
when   11   then   ' IsReplProc '
     
when   12   then   ' IsRule '
end
/*  Create the select  */
/*  Use @isobject variable isstead of IsUserTable string  */
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 ''' + @isobject + ''' ) = 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
     
if  ( @retval   =   0   and   @postcommand   is   not   null )
           
exec ( @postcommand )
     
return   @retval
GO

--下面2个例子:第一个 所有存储过程源代码,第2个修改所有表的所有者为dbo
sp_MSforeachObject 
4 , ' sp_helptext  '' ? '''
sp_MSforeachObject 
1 , ' sp_changeobjectowner  '' ? '' '' dbo '''  --当然这个可以应用sp_MSforeachtable 来完成

你可能感兴趣的:(foreach)