Technical Article

String Search

,

It's been rare in my career that I've needed to find a string of text within any and all of the many possible tables of a database. For the few occasions when I did, I would just go to Google, find something that worked, run it, and be done.

More recently my need for this type of script has increased, and I began to notice that most of the code out there was either slow (even when doing an extremely small search), didn't return all of the fields I wanted to see in the output, didn't search all of the character data types I was interested in, gave limited control / filter options (if any), and / or needed to be run from within the context of the database that was being searched (AKA: not feasible to make it a stored procedure unless you are only ever going to search within a specific database or deploy the procedure to all existing and future databases).

Hence my inspiration for creating my own stored procedure which allows one to search a multitude of data types in any specified database and includes some optional input parameter options / filters.

NOTE: Depending on your environment and needs (number of tables / columns / search criteria, etc.) these types of queries have the possibility to run long and have a performance impact on the system. Always use common sense when running queries such as these and limit your search via the input parameter options rather than casting as wide a net as possible to see what you will get back as results (especially in a production or shared environment).

The procedure accepts the following input parameters:

  • @Search_String: Text string you wish to search for
  • @Database_Name: Database whose table(s) and / or view(s) you wish to search against
  • @Object_Types: Object types to search against (U for tables, V for views; default is both)
  • @Data_Types: Data types to search against (default includes CHAR, NCHAR, NTEXT, NVARCHAR, TEXT, VARCHAR, and XML)
  • @Table_Max_Rows: Limits the search to objects of a maximum row count, avoiding scans against large datasets (default is no limit, though using this option will remove NTEXT, TEXT, XML, and views from the search)
  • @Column_Max_Length: Limits the search to columns of a maximum defined data length; handy for when you only want to search against short description fields (default is no limit)
  • @Creation_Source: Search objects created by users (U - default value), SQL Server (S), or both (B)

The procedure will output the following values:

  • object_type: Indicates if the object containing the search result is a table or view
  • is_ms_shipped: Indicates if the object was created by SQL Server
  • data_type: Indicates the data type of the column containing the search result
  • data_length: Indicates the column length, as defined in the database, containing the search result
  • database_name: Indicates the database name the search was performed against
  • schema_name: Indicates the schema name the search result was found in
  • object_name: Indicates the object name the search result was found in
  • column_name: Indicates the column name the search result was found in
  • column_data: Full contents of the column containing the search criteria match
  • column_data_xml: When the contents of the column_data field are XML then this column will return the same data but converted to the XML data type
  • occurrences: Count of the total number of occurrences of the full content of the column_data field

To execute the stored procedure you must at least populate the @Search_String and @Database_Name input parameters:

EXECUTE dbo.usp_String_Search
     @Search_String = N'random text'
    ,@Database_Name = N'my_database'

You can also use any or all of the optional input parameters to limit the scope of your search and produce results quicker while lessening the load on your server (recommended):

EXECUTE dbo.usp_String_Search
     @Search_String = N'random text'
    ,@Database_Name = N'my_database'
    ,@Object_Types = N'U'
    ,@Data_Types = N'CHAR, NCHAR, NVARCHAR, VARCHAR'
    ,@Table_Max_Rows = 10000
    ,@Column_Max_Length = 50
    ,@Creation_Source = N'U'

Any friendly feedback is always welcome. Enjoy!

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SET NOCOUNT ON


-----------------------------------------------------------------------------------------------------------------------------
--Error Trapping: Check If Procedure Already Exists And Create Shell If Applicable
-----------------------------------------------------------------------------------------------------------------------------

IF OBJECT_ID (N'dbo.usp_String_Search', N'P') IS NULL
BEGIN

EXECUTE ('CREATE PROCEDURE dbo.usp_String_Search AS SELECT 1 AS shell')

END
GO


-----------------------------------------------------------------------------------------------------------------------------
--Stored Procedure Details: Listing Of Standard Details Related To The Stored Procedure
-----------------------------------------------------------------------------------------------------------------------------

--Purpose: Search For A String Value Within Columns Of Data Types CHAR, NCHAR, NTEXT, NVARCHAR, TEXT, VARCHAR, XML
--Create Date (MM/DD/YYYY): 03/20/2012
--Developer: Sean Smith (s.smith.sql AT gmail DOT com)
--Additional Notes: N/A


-----------------------------------------------------------------------------------------------------------------------------
--Modification History: Listing Of All Modifications Since Original Implementation
-----------------------------------------------------------------------------------------------------------------------------

--Description: Added "is_ms_shipped" Field
--           : Added Option To Include System Objects In Search
--Date (MM/DD/YYYY): 12/06/2015
--Developer: Sean Smith (s.smith.sql AT gmail DOT com)
--Additional Notes: N/A


-----------------------------------------------------------------------------------------------------------------------------
--Main Query: Create Procedure
-----------------------------------------------------------------------------------------------------------------------------

ALTER PROCEDURE dbo.usp_String_Search

 @Search_String AS NVARCHAR (500)
,@Database_Name AS NVARCHAR (300)
,@Object_Types AS NVARCHAR (10) = NULL
,@Data_Types AS NVARCHAR (100) = NULL
,@Table_Max_Rows AS BIGINT = NULL
,@Column_Max_Length AS SMALLINT = NULL
,@Creation_Source AS NVARCHAR (1) = N'U'

WITH RECOMPILE

AS

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
SET ARITHABORT OFF
SET ARITHIGNORE ON
SET TEXTSIZE 2147483647


DECLARE
 @Creation_Filter AS NVARCHAR (30)
,@Loop_Column_Number AS BIGINT
,@Loop_Column_Number_First AS BIGINT
,@Loop_Object_Number AS BIGINT
,@Object_Name AS SYSNAME
,@Object_Prefix AS NVARCHAR (4)
,@Schema_Name AS SYSNAME
,@SQL_String_Full AS NVARCHAR (MAX)
,@SQL_String_IN_Column_Name AS NVARCHAR (MAX)
,@SQL_String_SELECT AS NVARCHAR (MAX)
,@SQL_String_WHERE AS NVARCHAR (MAX)


SET @Creation_Filter = (CASE @Creation_Source
WHEN N'S' THEN N'AND O.is_ms_shipped = 1'
WHEN N'U' THEN N'AND O.is_ms_shipped = 0'
ELSE N''
END)


SET @Object_Prefix = (CASE
WHEN @Creation_Source IN (N'B', N'S') THEN N'all_'
ELSE N''
END)


SET @Object_Types = N'''' + REPLACE (REPLACE (NULLIF (@Object_Types, N''), N' ', N''), N',', N''',''') + N''''


SET @Data_Types = N'''' + REPLACE (REPLACE (NULLIF (@Data_Types, N''), N' ', N''), N',', N''',''') + N''''


-----------------------------------------------------------------------------------------------------------------------------
--Error Trapping I: Validate "@Creation_Source", "@Database_Name", And "@Search_String" Input Parameter Values
-----------------------------------------------------------------------------------------------------------------------------

IF @Creation_Source NOT IN (N'B', N'S', N'U')
BEGIN

RAISERROR

(
 'ERROR: ''%s'' is not a valid creation source.

Valid Creation Sources:

B : Both (User and System created objects)
S : System created objects only
U : User created objects only'
,16
,1
,@Creation_Source
)


RETURN

END


IF EXISTS (SELECT * FROM master.sys.databases DB WHERE DB.name = @Database_Name)
BEGIN

SET @Database_Name = QUOTENAME (@Database_Name)

END
ELSE IF EXISTS (SELECT * FROM master.sys.databases DB WHERE QUOTENAME (DB.name) = @Database_Name + N']')
BEGIN

SET @Database_Name = @Database_Name + N']'

END
ELSE IF NOT EXISTS (SELECT * FROM master.sys.databases DB WHERE QUOTENAME (DB.name) = @Database_Name)
BEGIN

RAISERROR

(
 N'ERROR: Database (''%s'') does not exist. Make sure that the name is entered correctly.'
,16
,1
,@Database_Name
)


RETURN

END


IF NULLIF (@Search_String, N'') IS NOT NULL
BEGIN

SET @Search_String = REPLACE (REPLACE (REPLACE (REPLACE (@Search_String, N'[', N'[[]'), N'%', N'[%]'), N'_', N'[_]'), N'''', N'''''')

END
ELSE BEGIN

RAISERROR

(
 N'ERROR: @Search_String input parameter cannot be blank or NULL.'
,16
,1
)


RETURN

END


-----------------------------------------------------------------------------------------------------------------------------
--Error Trapping II: Check If Temp Table(s) Already Exist(s) And Drop If Applicable
-----------------------------------------------------------------------------------------------------------------------------

IF OBJECT_ID (N'tempdb.dbo.#temp_string_search_objects_columns', N'U') IS NOT NULL
BEGIN

DROP TABLE dbo.#temp_string_search_objects_columns

END


IF OBJECT_ID (N'tempdb.dbo.#temp_string_search_results', N'U') IS NOT NULL
BEGIN

DROP TABLE dbo.#temp_string_search_results

END


-----------------------------------------------------------------------------------------------------------------------------
--Table Creation: Create Temp Tables To Temporarily Store Searchable Objects / Columns And Output Results
-----------------------------------------------------------------------------------------------------------------------------

CREATE TABLE dbo.#temp_string_search_objects_columns

(
 object_type VARCHAR (2) NOT NULL
,is_ms_shipped BIT NOT NULL
,data_type SYSNAME NOT NULL
,data_length SMALLINT NOT NULL
,[schema_name] SYSNAME NOT NULL
,[object_name] SYSNAME NOT NULL
,column_name SYSNAME NOT NULL
,schema_object_dense_rank BIGINT NOT NULL
,column_row_number BIGINT UNIQUE NOT NULL
,PRIMARY KEY CLUSTERED
(
 schema_object_dense_rank
,column_row_number
)
)


CREATE TABLE dbo.#temp_string_search_results

(
 [schema_name] SYSNAME NOT NULL
,[object_name] SYSNAME NOT NULL
,column_name SYSNAME NOT NULL
,column_data NVARCHAR (MAX) NOT NULL
,occurrences INT NOT NULL
)


-----------------------------------------------------------------------------------------------------------------------------
--Table Update I: Insert Searchable Objects / Columns Into Temp Table
-----------------------------------------------------------------------------------------------------------------------------

SET @SQL_String_Full =

N'
SELECT
 O.[type] AS object_type
,O.is_ms_shipped
,LOWER (TYPE_NAME (C.user_type_id) + ISNULL ((N'': [ '' + (CASE
WHEN C.system_type_id <> C.user_type_id THEN TYPE_NAME (C.system_type_id)
END) + N'' ]''), N'''')) AS data_type
,(CASE
WHEN LOWER (TYPE_NAME (C.system_type_id)) IN (N''nchar'', N''ntext'', N''nvarchar'') THEN CONVERT (VARCHAR (6), C.max_length / 2)
WHEN LOWER (TYPE_NAME (C.system_type_id)) NOT IN (N''bigint'', N''bit'', N''date'', N''datetime'', N''datetime2'', N''datetimeoffset'', N''decimal'', N''float'', N''int'', N''money'', N''numeric'', N''real'', N''smalldatetime'', N''smallint'', N''smallmoney'', N''time'', N''tinyint'') THEN CONVERT (VARCHAR (6), C.max_length)
ELSE CONVERT (VARCHAR (6), C.max_length) + '' ('' + CONVERT (VARCHAR (11), COLUMNPROPERTY (C.[object_id], C.name, ''Precision'')) + '','' + ISNULL (CONVERT (VARCHAR (11), COLUMNPROPERTY (C.[object_id], C.name, ''Scale'')), 0) + '')''
END) AS data_length
,S.name AS [schema_name]
,O.name AS [object_name]
,C.name AS column_name
,DENSE_RANK () OVER
(
ORDER BY
 S.name
,O.name
) AS schema_object_dense_rank
,ROW_NUMBER () OVER
(
ORDER BY
C.name
) AS column_row_number
FROM
' + @Database_Name + N'.sys.schemas S
INNER JOIN ' + @Database_Name + N'.sys.' + @Object_Prefix + N'objects O ON O.[schema_id] = S.[schema_id]
AND O.[type] IN (' + (CASE
WHEN @Object_Types IS NOT NULL THEN @Object_Types
ELSE N'''U'', ''V'''
END) + N')
 '

 + @Creation_Filter +

N'
INNER JOIN ' + @Database_Name + N'.sys.' + @Object_Prefix + N'columns C ON C.[object_id] = O.[object_id]
 '


IF @Column_Max_Length > 0
BEGIN

SET @SQL_String_Full = @SQL_String_Full +

N'
' + NCHAR (9) + N'AND C.system_type_id IN (167, 175, 231, 239)
' + NCHAR (9) + N'AND (CASE
WHEN TYPE_NAME (C.system_type_id) IN (N''nchar'', N''ntext'', N''nvarchar'') THEN C.max_length / 2
ELSE C.max_length
END) BETWEEN 1 AND ' + CONVERT (NVARCHAR (6), @Column_Max_Length) + N'
 '

END
ELSE BEGIN

SET @SQL_String_Full = @SQL_String_Full +

N'
' + NCHAR (9) + N'AND C.system_type_id IN (35, 99, 167, 175, 231, 239, 241)
 '

END


SET @SQL_String_Full = @SQL_String_Full +

N'
INNER JOIN ' + @Database_Name + N'.sys.types T ON T.system_type_id = C.system_type_id
AND T.user_type_id = C.user_type_id
 ' + (CASE
WHEN @Data_Types IS NOT NULL THEN REPLICATE (NCHAR (9), 3) + N'AND T.name IN (' + @Data_Types + N')'
ELSE N''
END)


IF @Table_Max_Rows > 0
BEGIN

SET @SQL_String_Full = @SQL_String_Full +

N'
INNER JOIN

(
SELECT
DDPS.[object_id]
FROM
' + @Database_Name + N'.sys.dm_db_partition_stats DDPS
WHERE
DDPS.index_id < 2
GROUP BY
DDPS.[object_id]
HAVING
SUM (DDPS.row_count) <= ' + CONVERT (NVARCHAR (20), @Table_Max_Rows) + N'
) sqTMR ON sqTMR.[object_id] = O.[object_id]
 '

END


INSERT INTO dbo.#temp_string_search_objects_columns

(
 object_type
,is_ms_shipped
,data_type
,data_length
,[schema_name]
,[object_name]
,column_name
,schema_object_dense_rank
,column_row_number
)

EXECUTE (@SQL_String_Full)


-----------------------------------------------------------------------------------------------------------------------------
--Table Update II: Insert Matched Results Into Temp Table
-----------------------------------------------------------------------------------------------------------------------------

SELECT TOP (1)
 @Loop_Object_Number = X.schema_object_dense_rank
,@Schema_Name = X.[schema_name]
,@Object_Name = X.[object_name]
FROM
dbo.#temp_string_search_objects_columns X
ORDER BY
X.schema_object_dense_rank


WHILE @Loop_Object_Number IS NOT NULL
BEGIN

SET @SQL_String_SELECT = N''


SET @SQL_String_IN_Column_Name = N''


SET @SQL_String_WHERE = N''


SET @Loop_Column_Number = (SELECT MIN (X.column_row_number) FROM dbo.#temp_string_search_objects_columns X WHERE X.schema_object_dense_rank = @Loop_Object_Number)


SET @Loop_Column_Number_First = @Loop_Column_Number


WHILE @Loop_Column_Number IS NOT NULL
BEGIN

SELECT
 @SQL_String_SELECT = @SQL_String_SELECT + (CASE
WHEN @Loop_Column_Number = @Loop_Column_Number_First THEN N'SELECT' + NCHAR (13) + REPLICATE (NCHAR (9), 7) + N' '
ELSE NCHAR (13) + REPLICATE (NCHAR (9), 7) + N','
END) + N'CONVERT (NVARCHAR (MAX), (CASE
WHEN ' + (CASE
WHEN X.data_type = N'XML' THEN N'CONVERT (NVARCHAR (MAX), '
ELSE N''
END) + QUOTENAME (X.column_name) + (CASE
WHEN X.data_type = N'XML' THEN N')'
ELSE N''
END) + N' LIKE ''%' + @Search_String + N'%'' THEN ' + QUOTENAME (X.column_name) + N'
ELSE NULL
END)) AS ' + QUOTENAME (X.column_name)
,@SQL_String_IN_Column_Name = @SQL_String_IN_Column_Name + (CASE
WHEN @Loop_Column_Number = @Loop_Column_Number_First THEN N''
ELSE N', '
END) + QUOTENAME (X.column_name)
,@SQL_String_WHERE = @SQL_String_WHERE + (CASE
WHEN @Loop_Column_Number = @Loop_Column_Number_First THEN NCHAR (13) + REPLICATE (NCHAR (9), 6) + N'WHERE' + NCHAR (13) + REPLICATE (NCHAR (9), 7)
ELSE NCHAR (13) + REPLICATE (NCHAR (9), 7) + N'OR '
END) + (CASE
WHEN X.data_type = N'XML' THEN N'CONVERT (NVARCHAR (MAX), '
ELSE N''
END) + QUOTENAME (X.column_name) + (CASE
WHEN X.data_type = N'XML' THEN N')'
ELSE N''
END) + N' LIKE ''%' + @Search_String + N'%'''
FROM
dbo.#temp_string_search_objects_columns X
WHERE
X.schema_object_dense_rank = @Loop_Object_Number
AND X.column_row_number = @Loop_Column_Number


SET @Loop_Column_Number = (SELECT MIN (X.column_row_number) FROM dbo.#temp_string_search_objects_columns X WHERE X.schema_object_dense_rank = @Loop_Object_Number AND X.column_row_number > @Loop_Column_Number)

END


SET @SQL_String_Full =

(
N'
SELECT
 ''' + @Schema_Name + N''' AS [schema_name]
,''' + @Object_Name + N''' AS [object_name]
,UNPV.column_name
,UNPV.column_data
,COUNT (*) AS occurrences
FROM

(
'
+ @SQL_String_SELECT
+ NCHAR (13) + REPLICATE (NCHAR (9), 6) + N'FROM' + NCHAR (13)
+ REPLICATE (NCHAR (9), 7) + @Database_Name
+ N'.'
+ QUOTENAME (@Schema_Name)
+ N'.'
+ QUOTENAME (@Object_Name)
+ @SQL_String_WHERE
+ N'
) sqMAT

UNPIVOT

(
column_data FOR column_name IN

(
' + @SQL_String_IN_Column_Name + N'
)

) UNPV

GROUP BY
 UNPV.column_name
,UNPV.column_data
 '
)


BEGIN TRY

INSERT INTO dbo.#temp_string_search_results

(
 [schema_name]
,[object_name]
,column_name
,column_data
,occurrences
)

EXECUTE (@SQL_String_Full)

END TRY
BEGIN CATCH

END CATCH


SELECT TOP (1)
 @Loop_Object_Number = X.schema_object_dense_rank
,@Schema_Name = X.[schema_name]
,@Object_Name = X.[object_name]
FROM
dbo.#temp_string_search_objects_columns X
WHERE
X.schema_object_dense_rank = @Loop_Object_Number + 1
ORDER BY
X.schema_object_dense_rank


IF @@ROWCOUNT = 0
BEGIN

SET @Loop_Object_Number = NULL

END

END


-----------------------------------------------------------------------------------------------------------------------------
--Main Query: Final Display / Output
-----------------------------------------------------------------------------------------------------------------------------

SELECT
 (CASE Y.object_type
WHEN 'U' THEN 'Table'
WHEN 'V' THEN 'View'
ELSE 'ERROR'
END) AS object_type
,(CASE
WHEN Y.is_ms_shipped = 1 THEN 'Yes'
ELSE 'No'
END) AS is_ms_shipped
,Y.data_type
,Y.data_length
,DB_NAME (DB_ID (SUBSTRING (@Database_Name, 2, LEN (@Database_Name) - 2))) AS database_name
,Y.[schema_name]
,Y.[object_name]
,Y.column_name
,Z.column_data
,CONVERT (XML, (CASE
WHEN Y.data_type = N'XML' THEN Z.column_data
ELSE N''
END)) AS column_data_xml
,Z.occurrences
FROM
dbo.#temp_string_search_objects_columns Y
INNER JOIN dbo.#temp_string_search_results Z ON Z.[schema_name] = Y.[schema_name]
AND Z.[object_name] = Y.[object_name]
AND Z.column_name = Y.column_name
ORDER BY
 Y.object_type
,Y.is_ms_shipped
,Y.[schema_name]
,Y.[object_name]
,Y.column_name
,Z.column_data


-----------------------------------------------------------------------------------------------------------------------------
--Cleanup: Drop Any Remaining Temp Tables
-----------------------------------------------------------------------------------------------------------------------------

IF OBJECT_ID (N'tempdb.dbo.#temp_string_search_objects_columns', N'U') IS NOT NULL
BEGIN

DROP TABLE dbo.#temp_string_search_objects_columns

END


IF OBJECT_ID (N'tempdb.dbo.#temp_string_search_results', N'U') IS NOT NULL
BEGIN

DROP TABLE dbo.#temp_string_search_results

END
GO

Rate

4.9 (20)

You rated this post out of 5. Change rating

Share

Share

Rate

4.9 (20)

You rated this post out of 5. Change rating