When we write a query than is running for every database in the instance of SQL Server, we use Sp_msforeachdb.

If your query is bigger than 2000 chars, the query cannot work because Sp_msforeachdb has a character limitation of 2000 chars.

The solution is to re-create MySp_myforeachdb. We use sp_helptext to see the definition of sp_MSforeachdb and sp_MSforeach_worker, and we change to a new character limitation of 262144 chars.

Everything works as before but without character limitation of 2000 chars.

sp_Myforeachdb

  1
  2USE master;
  3GO
  4/*
  5 * This is the worker proc for all of the "for each" type procs.  Its function is to read the
  6 * next replacement name from the cursor (which returns only a single name), plug it into the
  7 * replacement locations for the commands, and execute them.  It assumes the cursor "hCForEach***"
  8 * has already been opened by its caller.
  9 * worker_type is a parameter that indicates whether we call this for a database (1) or for a table (0)
 10 */
 11create proc dbo.sp_Myforeach_worker
 12 @command1 nvarchar(max), @replacechar nchar(1) = N'?', @command2 nvarchar(max) = null, @command3 nvarchar(max) = null, @worker_type int =1
 13as
 14
 15 create table #qtemp ( /* Temp command storage */
 16  qnum    int    NOT NULL,
 17  qchar    nvarchar(max) COLLATE database_default NULL
 18 )
 19
 20 set nocount on
 21 declare @name nvarchar(517), @namelen int, @q1 nvarchar(max), @q2 nvarchar(max)
 22   declare @q3 nvarchar(max), @q4 nvarchar(max), @q5 nvarchar(max)
 23 declare @q6 nvarchar(max), @q7 nvarchar(max), @q8 nvarchar(max), @q9 nvarchar(max), @q10 nvarchar(max)
 24 declare @cmd nvarchar(max), @replacecharindex int, @useq tinyint, @usecmd tinyint, @nextcmd nvarchar(max)
 25   declare @namesave nvarchar(517), @nametmp nvarchar(517), @nametmp2 nvarchar(258)
 26
 27 declare @local_cursor cursor
 28 if @worker_type=1
 29  set @local_cursor = hCForEachDatabase
 30 else
 31  set @local_cursor = hCForEachTable
 32
 33 open @local_cursor
 34 fetch @local_cursor into @name
 35
 36 /* Loop for each database */
 37 while (@@fetch_status >= 0) begin
 38  /* Initialize. */
 39
 40      /* save the original dbname */
 41      select @namesave = @name
 42  select @useq = 1, @usecmd = 1, @cmd = @command1, @namelen = datalength(@name)
 43  while (@cmd is not null) begin  /* Generate @q* for exec() */
 44   /*
 45    * Parse each @commandX into a single executable batch.
 46    * Because the expanded form of a @commandX may be > OSQL_MAXCOLLEN_SET, we'll need to allow overflow.
 47    * We also may append @commandX's (signified by '++' as first letters of next @command).
 48    */
 49   select @replacecharindex = charindex(@replacechar, @cmd)
 50   while (@replacecharindex <> 0) begin
 51
 52            /* 7.0, if name contains ' character, and the name has been single quoted in command, double all of them in dbname */
 53            /* if the name has not been single quoted in command, do not doulbe them */
 54            /* if name contains ] character, and the name has been [] quoted in command, double all of ] in dbname */
 55            select @name = @namesave
 56            select @namelen = datalength(@name)
 57            declare @tempindex int
 58            if (substring(@cmd, @replacecharindex - 1, 1) = N'''') begin
 59               /* if ? is inside of '', we need to double all the ' in name */
 60               select @name = REPLACE(@name, N'''', N'''''')
 61            end else if (substring(@cmd, @replacecharindex - 1, 1) = N'[') begin
 62               /* if ? is inside of [], we need to double all the ] in name */
 63               select @name = REPLACE(@name, N']', N']]')
 64            end else if ((@name LIKE N'%].%]') and (substring(@name, 1, 1) = N'[')) begin
 65               /* ? is NOT inside of [] nor '', and the name is in [owner].[name] format, handle it */
 66               /* !!! work around, when using LIKE to find string pattern, can't use '[', since LIKE operator is treating '[' as a wide char */
 67               select @tempindex = charindex(N'].[', @name)
 68               select @nametmp  = substring(@name, 2, @tempindex-2 )
 69               select @nametmp2 = substring(@name, @tempindex+3, len(@name)-@tempindex-3 )
 70               select @nametmp  = REPLACE(@nametmp, N']', N']]')
 71               select @nametmp2 = REPLACE(@nametmp2, N']', N']]')
 72               select @name = N'[' + @nametmp + N'].[' + @nametmp2 + ']'
 73            end else if ((@name LIKE N'%]') and (substring(@name, 1, 1) = N'[')) begin
 74               /* ? is NOT inside of [] nor '', and the name is in [name] format, handle it */
 75       /* j.i.c., since we should not fall into this case */
 76               /* !!! work around, when using LIKE to find string pattern, can't use '[', since LIKE operator is treating '[' as a wide char */
 77               select @nametmp = substring(@name, 2, len(@name)-2 )
 78               select @nametmp = REPLACE(@nametmp, N']', N']]')
 79               select @name = N'[' + @nametmp + N']'
 80            end
 81            /* Get the new length */
 82            select @namelen = datalength(@name)
 83
 84            /* start normal process */
 85    if (datalength(@cmd) + @namelen - 1 > 262144) begin
 86     /* Overflow; put preceding stuff into the temp table */
 87     if (@useq > 9) begin
 88      close @local_cursor
 89      if @worker_type=1
 90       deallocate hCForEachDatabase
 91      else
 92       deallocate hCForEachTable
 93
 94      RAISERROR(55555, 16, 1); -- N'sp_MSforeach_worker assert failed:  command too long'
 95      return 1
 96     end
 97     if (@replacecharindex < @namelen) begin
 98      /* If this happened close to beginning, make sure expansion has enough room. */
 99      /* In this case no trailing space can occur as the row ends with @name. */
100      select @nextcmd = substring(@cmd, 1, @replacecharindex)
101      select @cmd = substring(@cmd, @replacecharindex + 1, 262144)
102      select @nextcmd = stuff(@nextcmd, @replacecharindex, 1, @name)
103      select @replacecharindex = charindex(@replacechar, @cmd)
104      insert #qtemp values (@useq, @nextcmd)
105      select @useq = @useq + 1
106      continue
107     end
108     /* Move the string down and stuff() in-place. */
109     /* Because varchar columns trim trailing spaces, we may need to prepend one to the following string. */
110     /* In this case, the char to be replaced is moved over by one. */
111     insert #qtemp values (@useq, substring(@cmd, 1, @replacecharindex - 1))
112     if (substring(@cmd, @replacecharindex - 1, 1) = N' ') begin
113      select @cmd = N' ' + substring(@cmd, @replacecharindex, 262144)
114      select @replacecharindex = 2
115     end else begin
116      select @cmd = substring(@cmd, @replacecharindex, 262144)
117      select @replacecharindex = 1
118     end
119     select @useq = @useq + 1
120    end
121    select @cmd = stuff(@cmd, @replacecharindex, 1, @name)
122    select @replacecharindex = charindex(@replacechar, @cmd)
123   end
124
125   /* Done replacing for current @cmd.  Get the next one and see if it's to be appended. */
126   select @usecmd = @usecmd + 1
127   select @nextcmd = case (@usecmd) when 2 then @command2 when 3 then @command3 else null end
128   if (@nextcmd is not null and substring(@nextcmd, 1, 2) = N'++') begin
129    insert #qtemp values (@useq, @cmd)
130    select @cmd = substring(@nextcmd, 3, 262144), @useq = @useq + 1
131    continue
132   end
133
134   /* Now exec() the generated @q*, and see if we had more commands to exec().  Continue even if errors. */
135   /* Null them first as the no-result-set case won't. */
136   select @q1 = null, @q2 = null, @q3 = null, @q4 = null, @q5 = null, @q6 = null, @q7 = null, @q8 = null, @q9 = null, @q10 = null
137   select @q1 = qchar from #qtemp where qnum = 1
138   select @q2 = qchar from #qtemp where qnum = 2
139   select @q3 = qchar from #qtemp where qnum = 3
140   select @q4 = qchar from #qtemp where qnum = 4
141   select @q5 = qchar from #qtemp where qnum = 5
142   select @q6 = qchar from #qtemp where qnum = 6
143   select @q7 = qchar from #qtemp where qnum = 7
144   select @q8 = qchar from #qtemp where qnum = 8
145   select @q9 = qchar from #qtemp where qnum = 9
146   select @q10 = qchar from #qtemp where qnum = 10
147   truncate table #qtemp
148   exec (@q1 + @q2 + @q3 + @q4 + @q5 + @q6 + @q7 + @q8 + @q9 + @q10 + @cmd)
149   select @cmd = @nextcmd, @useq = 1
150  end /* while @cmd is not null, generating @q* for exec() */
151
152  /* All commands done for this name.  Go to next one. */
153  fetch @local_cursor into @name
154 end /* while FETCH_SUCCESS */
155 close @local_cursor
156 if @worker_type=1
157  deallocate hCForEachDatabase
158 else
159  deallocate hCForEachTable
160
161 return 0
162
163GO
164
165GO
166CREATE PROC sp_Myforeachdb
167 @command1 nvarchar(max), @replacechar nchar(1) = N'?', @command2 nvarchar(max) = null, @command3 nvarchar(max) = null,
168 @precommand nvarchar(max) = null, @postcommand nvarchar(max) = null
169 AS
170
171 set deadlock_priority low
172
173 /* This proc returns one or more rows for each accessible db, with each db defaulting to its own result set */
174 /* @precommand and @postcommand may be used to force a single result set via a temp table. */
175
176 /* Preprocessor won't replace within quotes so have to use str(). */
177 declare @inaccessible nvarchar(12), @invalidlogin nvarchar(12), @dbinaccessible nvarchar(12)
178 select @inaccessible = ltrim(str(convert(int, 0x03e0), 11))
179 select @invalidlogin = ltrim(str(convert(int, 0x40000000), 11))
180 select @dbinaccessible = N'0x80000000'  /* SQLDMODbUserProf_InaccessibleDb; the negative number doesn't work in convert() */
181
182 if (@precommand is not null)
183  exec(@precommand)
184
185 declare @origdb nvarchar(128)
186 select @origdb = db_name()
187
188 /* 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. */
189   /* Create the select */
190 exec(N'declare hCForEachDatabase cursor global for select name from master.dbo.sysdatabases d ' +
191   N' where (d.status &amp; ' + @inaccessible + N' = 0)' +
192   N' and (DATABASEPROPERTYEX(d.name, ''UserAccess'') <> ''SINGLE_USER'' and (has_dbaccess(d.name) = 1))' )
193
194 declare @retval int
195 select @retval = @@error
196 if (@retval = 0)
197  exec @retval = dbo.sp_Myforeach_worker @command1, @replacechar, @command2, @command3, 1
198
199 if (@retval = 0 and @postcommand is not null)
200  exec(@postcommand)
201
202   declare @tempdb nvarchar(258)
203   SELECT @tempdb = REPLACE(@origdb, N']', N']]')
204   exec (N'use ' + N'[' + @tempdb + N']')
205
206 return @retval
207GO