sp_MSforeachtable 与 sp_MSforeachdb

时间:2023-03-10 01:29:57
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 来完成