I needed this functionality recently and nothing out there, either articles or here on Stack Overflow was of much use, everything either didn't work correctly, had errors or was dreadfully slow.
I created the following procedure which can be used to search for text in any column in any table in any SQL Server database.
Note it's a work-in-progress and [as of now] has only existed for a couple of days, I'll update with any bug fixes.
It's reasonably fast, has some customisation options that I needed to help with large databases and does not use any loops / cursors.
It's a System Procedure, meaning you create it in Master
and can then use it in the context of any database.
It returns results showing the table(s), the column(s) (and data types) containing the searched for text - and a SQL snippet to quickly cut and paste to find the relevant rows.
Parameter descriptions:
@Find - Required - 'text to search for, including any wildcards'
@Schema - Optional - only search tables in specific schema
@IncludeMax - Optional - Specify to search in Varchar(max) columns, default is to ignore
@IncludeHistory - Optional - Specify to include searching in system-versioned tables, default is to ignore, assumes schema name is "history"
@IncludeAudit - Optional - Specify to search CreatedBy / AlteredBy columns, default is to ignore
@MaxTables - Optional - Limits how many tables to search - default is -1 for no limit
@TnameList - Optional - Provide a comma-delimited list of terms to match with or ignore tables, prefix with "~" for exclude list
@CnameList - Optional - Provide a comma-delimited list of terms to match with or ignore columns, prefix with "~" for exclude list
@RowLimit - Optional - Limits the search to tables with fewer than N rows, default is 100k rows
@Refresh - Optional - Search results are stored and retrieved per search term/database - use this to re-do the search
Some example usage:
exec sp_FindTextInAnyTable @Find = '%textToFind%';
/* Find the search term within text any column in any table using default options */
exec sp_FindTextInAnyTable @Find = '%myname@mydomain%', @Refresh = 1; /* Repeat a previous search and check all tables again instead of returning cached results */
exec sp_FindTextInAnyTable @Find = '%textToFind%', @IncludeMax = 1;
/* Also search any varchar(max) column */
exec sp_FindTextInAnyTable @Find = '%textToFind%', @RowLimit = -1;
/* Search any table regardless of size */
exec sp_FindTextInAnyTable @Find = '%textToFind%', @TnameList = 'config, setting'
/* Search only in tables with the text "config" or "setting" in the name */
exec sp_FindTextInAnyTable @Find = '%textToFind%', @TnameList = '~notes'
/* Search all tables except any with "notes" in the name */
Here's the procedure definition:
USE MASTER;
GO
create or alter procedure sp_FindTextInAnyTable
@Find nvarchar(100), /* Like predicate - supply wildcards in the value */
@SearchNumbers tinyint = 0, /* If searching for numbers: 0 = look at text type columns only 1 = look at numeric type columns only, 2 = look at all types */
@Schema sysname = null, /* search only tables in a specific schema or NULL for all schemas */
@IncludeMax bit = 0, /* Set to 1 to include varchar(max) columns, by default these are excluded */
@IncludeHistory bit = 0, /* Set to 1 to include history tables, by default these are excluded */
@IncludeAudit bit = 0, /* Set to 1 to include CreatedBy / AlteredBy columns, by default these are excluded */
@MaxTables int = -1, /* Set the maximum number of tables to search, set to -1 for no limit (be careful and only run on a replica in production) */
@TnameList varchar(200) = null, /* Comma-delimited list of words to match with table names to search or NULL to search all tables; prefix the list with ~ to exclude tables instead */
@CnameList varchar(200) = null, /* Comma-delimited list of words to match with column names to search or NULL to search all columns; prefix the list with ~ to exclude columns instead (except audit columns) */
@RowLimit bigint = 100000, /* Default max size of table in rows - prevents hitting large tables unless required, ignored if specific list of tables used */
@Refresh bit = 0, /* Set to 1 to have existing search re-evaluated, otherwise data is returned from last search result */
@Debug bit = 0
as
set nocount, xact_abort on;
/*
Run this in the context of any database to search within every CHAR or VARCHAR column
in every table in the database for the specified text to find.
If the text exists in any row it returns the column and table containing the text and
the specific sql to run to find the text in the table.
example usage
exec sp_FindTextInAnyTable @Find = 'black'; /* Find any table with specific value "black" in any column, excludes any history tables, audit columns and varchar(max) columns, limits to first 50 tables */
exec sp_FindTextInAnyTable @Find = '%spoke%'; /* Find the text "spoke" within text any column in any table */
exec sp_FindTextInAnyTable @Find = '%sys%', @IncludeAudit = 1; /* Find within any column in any table, include searching within createdby/alteredby columns */
exec sp_FindTextInAnyTable @Find = '%sys%', @Refresh = 1; /* Repeat a previous search and check all tables again instead of returning cached results */
exec sp_FindTextInAnyTable @Find = '%scot%', @TnameList = 'file, log', @Refresh = 1; /*Find any column containing the text in only in tables with "file" or "log" in its name */
exec sp_FindTextInAnyTable @Find = '%scot%', @TnameList = 'file, log', @IncludeHistory = 1, @Refresh = 1; /* Repeat the above search but also search in History tables */
exec sp_FindTextInAnyTable @Find = '%scot%', @Refresh = 1, @Schema = 'history' /* Only search the History tables */
exec sp_FindTextInAnyTable @Find = '%scot%', @TnameList = '~file, log',@Refresh = 1; /* Search all tables except those with file or log in the name */
*/
declare @sql nvarchar(max), @br varchar(2) = Iif(@Debug = 1, Char(13), ''), @Valid bit = 0,
@TExclude bit = (Iif(@TnameList like '~%', 1, 0)), @CExclude bit = (Iif(@CnameList like '~%', 1, 0));
declare @Union varchar(20) = Concat(' union all ', @br);
declare @TNames table(tname sysname);
declare @CNames table(cname sysname);
insert into @TNames(tname)
select Trim([value])
from String_Split(Replace(@TnameList, '~',''), ',');
insert into @CNames(cname)
select Trim([value])
from String_Split(Replace(@CnameList, '~',''), ',');
if Object_Id('master.dbo.sp_FindTextInAnyTableResults') is null
begin;
create table master.dbo.sp_FindTextInAnyTableResults(
DBId int not null,
Find nvarchar(100) not null,
Tname sysname not null,
Cname sysname not null,
Row_Count bigint not null,
Datatype varchar(50), [SQL] varchar(max) not null,
CreateDate datetime2(0) not null default(GetDate()),
Id int identity
);
alter table dbo.sp_FindTextInAnyTableResults add constraint [PK_sp_FindTextInAnyTableResults] primary key clustered (dbid, Find, Id) with (fillfactor = 100) on [PRIMARY];
end;
if @Refresh = 1 or @Debug = 1 or not exists (select * from master.dbo.sp_FindTextInAnyTableResults where DBId = Db_Id() and Find = @Find)
begin
delete from master.dbo.sp_FindTextInAnyTableResults where DBId = Db_Id() and Find = @Find;
with TList as (
select Concat_Ws('.', QuoteName(Schema_Name(t.schema_id)),
QuoteName(t.[name])) TName,
c.[name] CName,
dt.[name] DTName,
dt.system_type_id,
c.max_length,
c.precision, c.scale,
Dense_Rank() over(order by t.[name]) Tcount,
row_count
from sys.columns c
join sys.dm_db_partition_stats p on p.object_id = c.object_id and index_id < 2 and row_count > 0
join sys.tables t on t.object_id = c.object_id and (@Schema is null or t.schema_id = Schema_Id(@Schema)) and Schema_Name(t.schema_id) != 'datasync'
join sys.types dt on dt.user_type_id = c.user_type_id
where (
(dt.system_type_id in (167,175,231,239) and (@SearchNumbers in (0,2) or (Try_Convert(int, @Find) is null and Try_Convert(decimal, @Find) is null and Try_Convert(money, @Find) is null)))
or (@SearchNumbers > 0 and Try_Convert(int, @Find) is not null and (dt.[name] like '%int%') )
or (@SearchNumbers > 0 and Try_Convert(decimal, @Find) is not null and (dt.[name] like '%decimal%' or dt.[name] like '%numeric%' or dt.[name] like '%real%' or dt.[name] like '%float%') )
or (@SearchNumbers > 0 and Try_Convert(money, @Find) is not null and (dt.[name] like '%money%') )
)
and (@IncludeHistory = 1 or Schema_Name(t.schema_id) != 'History' or @Schema = 'History')
and (c.max_length >= Len(@Find) or (c.max_length = -1 and @IncludeMax = 1) or (dt.[name] not like '%char%'))
and (@IncludeAudit = 1 or not (c.[name] = 'createdby' or c.[name] = 'alteredby'))
and (@TnameList is null or (
(@TExclude = 0 and exists (select * from @TNames where CharIndex(tname, t.[name]) > 0)) or
(@TExclude = 1 and not exists (select * from @TNames where CharIndex(tname, t.[name]) > 0))
)
)
and (@CnameList is null or (
(@CExclude = 0 and exists (select * from @CNames where CharIndex(cname, c.[name]) > 0)) or
(@CExclude = 1 and not exists (select * from @CNames where CharIndex(cname, c.[name]) > 0))
)
)
and (@RowLimit = -1 or (@TnameList is not null and @TExclude = 0) or row_count <= @RowLimit)
and is_computed = 0 and is_hidden = 0
)
/*select * from tlist order by 1,2 end;*/
select @sql =
Concat(
'insert into master.dbo.sp_FindTextInAnyTableResults(DBId, Find, Tname, Cname, Row_Count, DataType, [SQL])',
@br,
String_Agg([sql], @Union)
),
@Valid = IsNull(Max(Iif([sql] is not null, 1, 0)), 0)
from (
select Convert(varchar(max),
Concat(
'select top(1) db_id(), '
, '''', @Find, ''''
, ', ''', TName, ''''
, ', ''', CName, ''''
, ', ', row_count
, ', DataType = ',
Concat(
''''
, DTName
, case when system_type_id in (167,175,231,239) then Iif(max_length = -1, '(max)', Concat('(', max_length, ')')) end
, case when DTName like '%decimal%' or DTName like '%numeric%' or DTName like '%real%' or DTName like '%float%' then '(' + Concat_Ws(',', precision, scale) + ')' end
, ''''
)
, ', SqlToRun = ',
Concat(''''
, 'Select * from '
, QuoteName(Db_Name()), '.', TName
, ' where '
, case when system_type_id in (167,175,231,239) then QuoteName(CName) else Concat('try_convert(varchar(50), ', QuoteName(CName), ')') end
, ' like ', ''''''
, case when system_type_id in (167,175,231,239) then @Find else Concat('%', @Find, '%') end
, '''''', ''''
)
, ' from ', TName
, ' where ', case when system_type_id in (167,175,231,239) then QuoteName(CName) else Concat('try_convert(varchar(50), ', QuoteName(CName), ')') end
, ' like '''
, case when system_type_id in (167,175,231,239) then @Find else Concat('%', @Find, '%') end,
'''')
)[sql]
from TList
where @MaxTables = -1
or Tcount <= @MaxTables
or @TnameList is not null
)[sql];
if @Debug = 1
begin
select @sql;
print @sql;
end;
else
if @Valid = 1 exec (@sql);
end;
select Concat(
'Found in table '
, Tname
, ', Rows = ', Max(Row_Count)
, ', Column', Iif(Count(*) > 1, 's', ''), ': '
, String_Agg(Concat_Ws(' ', Cname, Datatype), ', ')
) FoundIn,
String_Agg([SQL], ';' + Char(13)) + ';' [SQL]
from master.dbo.sp_FindTextInAnyTableResults
where DBId = Db_Id() and Find = @Find
group by Tname
order by Tname;
go
go
After creating the procedure in master
register as a system procedure:
exec sys.sp_MS_marksystemobject 'sp_FindTextInAnyTable'