How to find text in stored procedures in SQL Server
Search the code of every stored procedure, function, view and trigger for a table, column or phrase. Why INFORMATION_SCHEMA misses matches, how to search all databases, and how to find real dependencies.
You’re about to rename a column, drop a table or change what a status code means, and you need to know which procedures use it. SQL Server keeps the source of every procedure, function, view and trigger, so you can search it with a query. The trick is to search the right catalog view, because one of the obvious ones silently misses matches.
The examples run against the SpDevDemo database.
The query
DECLARE @find NVARCHAR(200) = N'Cancelled';
SELECT
s.name AS schema_name,
o.name AS object_name,
o.type_desc,
o.modify_date
FROM sys.sql_modules AS m
JOIN sys.objects AS o ON o.object_id = m.object_id
JOIN sys.schemas AS s ON s.schema_id = o.schema_id
WHERE m.definition LIKE N'%' + @find + N'%'
ORDER BY o.type_desc, s.name, o.name;
sys.sql_modules.definition holds the full text of every T-SQL module in the database: procedures, functions, views, triggers. It’s NVARCHAR(MAX), so nothing is cut off. The search is case-insensitive when the database collation is.
To show where in the code the match is, add a snippet:
SUBSTRING(m.definition, CHARINDEX(@find, m.definition) - 60, 160) AS context
Why not INFORMATION_SCHEMA.ROUTINES?
It’s the first answer you’ll find online, and it misses matches. INFORMATION_SCHEMA.ROUTINES.ROUTINE_DEFINITION holds only the first 4,000 characters of each routine. Anything after that isn’t searched.
Create a procedure of about 6,100 characters with the word near the end, and compare:
DECLARE @sql NVARCHAR(MAX) =
N'CREATE OR ALTER PROCEDURE dbo.LongProcedure AS BEGIN '
+ REPLICATE(CAST(N'SELECT 1 AS filler; ' AS NVARCHAR(MAX)), 300)
+ N'SELECT OrderId FROM dbo.Orders WHERE Status = ''Cancelled''; END';
EXEC (@sql);
SELECT
LEN(OBJECT_DEFINITION(OBJECT_ID('dbo.LongProcedure'))) AS full_length,
(SELECT LEN(ROUTINE_DEFINITION) FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME = 'LongProcedure') AS information_schema_length,
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME = 'LongProcedure' AND ROUTINE_DEFINITION LIKE '%Cancelled%') AS found_by_information_schema,
(SELECT COUNT(*) FROM sys.sql_modules
WHERE object_id = OBJECT_ID('dbo.LongProcedure') AND definition LIKE '%Cancelled%') AS found_by_sql_modules;
full_length information_schema_length found_by_information_schema found_by_sql_modules
----------- ------------------------- --------------------------- --------------------
6108 4000 0 1
The old sys.syscomments view has a similar problem: it splits long definitions into 4,000-character rows, so a word that straddles two rows isn’t found. Use sys.sql_modules, or OBJECT_DEFINITION(object_id), which returns the same full text.
Searching for names with brackets or underscores
LIKE treats _ as “any one character”, % as “anything”, and [...] as a set of characters. Searching for Order_Lines also matches OrderXLines, and searching for [Orders] matches any single letter from that set. Escape them:
SELECT o.name
FROM sys.sql_modules AS m
JOIN sys.objects AS o ON o.object_id = m.object_id
WHERE m.definition LIKE N'%Order[_]Lines%' -- [_] is a literal underscore
OR m.definition LIKE N'%[[]Orders]%'; -- [[] is a literal [
For an exact substring with no wildcards at all, CHARINDEX(@find, m.definition) > 0 avoids the question.
Searching every database
Catalog views are per database. To search all of them, loop over sys.databases with dynamic SQL:
DECLARE @find NVARCHAR(200) = N'Cancelled';
DECLARE @sql NVARCHAR(MAX) = N'';
SELECT @sql += N'
SELECT ' + QUOTENAME(name, '''') + N' AS database_name, s.name AS schema_name, o.name AS object_name, o.type_desc
FROM ' + QUOTENAME(name) + N'.sys.sql_modules AS m
JOIN ' + QUOTENAME(name) + N'.sys.objects AS o ON o.object_id = m.object_id
JOIN ' + QUOTENAME(name) + N'.sys.schemas AS s ON s.schema_id = o.schema_id
WHERE CHARINDEX(@find, m.definition) > 0
UNION ALL'
FROM sys.databases
WHERE database_id > 4 -- skip master, tempdb, model, msdb
AND state_desc = 'ONLINE'
AND HAS_DBACCESS(name) = 1;
IF @sql <> N''
BEGIN
SET @sql = LEFT(@sql, LEN(@sql) - LEN(N'UNION ALL')) + N' ORDER BY database_name, object_name;';
EXEC sp_executesql @sql, N'@find NVARCHAR(200)', @find = @find;
END
sp_MSforeachdb is often suggested for this. It’s undocumented, and it has been known to skip databases, so the loop above is the safer choice.
Text search vs real dependencies
Text search finds the word everywhere: in comments, in strings, in other tables’ names (Orders matches OrderLines too). When the question is “what uses this table or column”, SQL Server’s dependency views are more precise:
SELECT
r.referencing_schema_name,
r.referencing_entity_name,
d.referenced_minor_name AS column_name
FROM sys.dm_sql_referencing_entities('dbo.Orders', 'OBJECT') AS r
CROSS APPLY sys.dm_sql_referenced_entities(
QUOTENAME(r.referencing_schema_name) + '.' + QUOTENAME(r.referencing_entity_name), 'OBJECT') AS d
WHERE d.referenced_entity_name = 'Orders'
AND d.referenced_minor_name = 'Status';
referencing_schema_name referencing_entity_name column_name
----------------------- ----------------------- -----------
dbo LongProcedure Status
That lists every module that references the Status column of dbo.Orders. sys.dm_sql_referenced_entities raises an error if one of the modules refers to an object that no longer exists; that’s worth knowing about anyway. The dependency views can’t see table names inside dynamic SQL strings. Use both: dependencies for precision, text search to catch dynamic SQL and comments.
Beyond the database
Not all T-SQL lives in modules. Also check:
- SQL Server Agent jobs:
msdb.dbo.sysjobsteps.command. - Your application code for inline SQL and ORM mappings.
- SSIS packages and reports, which embed their own queries.
- Your source control. If every procedure lives as a
.sqlfile in a repository, a plain text search in your editor covers everything at once, including procedures that haven’t been deployed yet. That’s also how SpDev works: it debugs and profiles procedures from their.sqlfiles in your workspace.