How to Track Stored Procedure Deletion: Find Who Dropped It and When in SQL Server


10 views

When a critical SQL Server job fails with an error indicating a missing stored procedure, it's essential to investigate:

-- Example error message you might encounter
Msg 2812, Level 16, State 62, Line 1
Could not find stored procedure 'usp_CriticalProcess'.

SQL Server maintains a default trace that records schema changes:

SELECT 
    te.name AS EventType,
    t.DatabaseName,
    t.ObjectName,
    t.StartTime,
    t.LoginName,
    t.ApplicationName,
    t.HostName
FROM 
    fn_trace_gettable(
        (SELECT REVERSE(SUBSTRING(REVERSE(path), 
        CHARINDEX('\', REVERSE(path)), 256)) + 'log.trc'
        FROM sys.traces WHERE is_default = 1), DEFAULT) t
JOIN 
    sys.trace_events te ON t.EventClass = te.trace_event_id
WHERE 
    t.EventClass = 47 -- Object:Deleted
    AND t.ObjectType = 8272 -- Stored Procedure
    AND t.ObjectName = 'usp_CriticalProcess'
ORDER BY 
    t.StartTime DESC;

If server audit is configured, check the audit logs:

SELECT 
    event_time,
    server_principal_name,
    object_name,
    statement
FROM 
    sys.fn_get_audit_file('C:\Audits\*.sqlaudit', DEFAULT, DEFAULT)
WHERE 
    action_id = 'DRP' -- DROP action
    AND object_name = 'usp_CriticalProcess'
ORDER BY 
    event_time DESC;

If CDC is enabled on the database:

-- Check if CDC is enabled on the database
SELECT name, is_cdc_enabled FROM sys.databases WHERE name = DB_NAME();

-- Query CDC capture instance for schema changes
SELECT 
    __$operation,
    __$update_mask,
    __$command_id,
    __$start_lsn,
    __$seqval,
    *
FROM 
    cdc.ddl_history
WHERE 
    object_name = 'usp_CriticalProcess'
ORDER BY 
    __$start_lsn DESC;

Implement these solutions to avoid similar issues:

-- 1. Create a database trigger to log schema changes
CREATE TRIGGER tr_AuditSchemaChanges
ON DATABASE
FOR DROP_PROCEDURE, ALTER_PROCEDURE
AS
BEGIN
    INSERT INTO SchemaChangeLog
    (ChangeType, ObjectName, UserName, ChangeDate)
    VALUES
    (EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]','nvarchar(100)'),
     EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(255)'),
     EVENTDATA().value('(/EVENT_INSTANCE/LoginName)[1]','nvarchar(255)'),
     GETDATE())
END;

Consider implementing source control for database objects to track all changes systematically.


When a scheduled job fails with errors indicating a missing stored procedure, database administrators need to quickly determine:

  • Exactly when the procedure was deleted
  • Which user account performed the deletion

The most reliable approaches for tracking schema changes in SQL Server:

1. Querying the Default Trace

SQL Server maintains a rolling trace that captures schema changes:

SELECT 
    te.name AS EventName,
    t.DatabaseName,
    t.StartTime,
    t.LoginName,
    t.ApplicationName,
    t.HostName,
    t.TextData
FROM sys.fn_trace_gettable(
    CONVERT(VARCHAR(150), 
        (SELECT TOP 1 f.[value] 
         FROM sys.fn_trace_getinfo(NULL) f 
         WHERE f.property = 2)), DEFAULT) t
JOIN sys.trace_events te ON t.EventClass = te.trace_event_id
WHERE te.name IN ('Object:Deleted', 'Object:Altered')
AND t.TextData LIKE '%YourProcedureName%'
ORDER BY t.StartTime DESC;

2. Using SQL Server Audit Feature

If database auditing is configured (recommended):

SELECT 
    event_time,
    server_principal_name,
    object_name,
    statement
FROM sys.fn_get_audit_file('C:\Audits\*.sqlaudit', DEFAULT, DEFAULT)
WHERE action_id = 'DR' -- Delete object event
AND object_name = 'YourProcedureName'
ORDER BY event_time DESC;

3. Checking SQL Server Logs

For instances without auditing configured:

EXEC sp_readerrorlog 0, 1, 'DROP PROCEDURE', 'YourProcedureName';

Implement these best practices to avoid future issues:

-- Create database audit specification
USE [YourDatabase];
GO
CREATE DATABASE AUDIT SPECIFICATION [SchemaChanges]
FOR SERVER AUDIT [ServerAudit]
ADD (SCHEMA_OBJECT_CHANGE_GROUP),
ADD (DATABASE_OBJECT_CHANGE_GROUP);
GO

-- Enable the audit
ALTER SERVER AUDIT [ServerAudit] WITH (STATE = ON);
GO

Create a logging table and trigger:

CREATE TABLE dbo.SchemaChangeLog (
    LogID INT IDENTITY(1,1) PRIMARY KEY,
    EventTime DATETIME DEFAULT GETDATE(),
    LoginName NVARCHAR(128),
    HostName NVARCHAR(128),
    EventType NVARCHAR(128),
    ObjectName NVARCHAR(128),
    TSQLCommand NVARCHAR(MAX)
);

CREATE TRIGGER tr_AuditStoredProcedures
ON DATABASE
FOR DROP_PROCEDURE, ALTER_PROCEDURE
AS
BEGIN
    INSERT INTO dbo.SchemaChangeLog (
        LoginName,
        HostName,
        EventType,
        ObjectName,
        TSQLCommand
    )
    SELECT
        ORIGINAL_LOGIN(),
        HOST_NAME(),
        EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(128)'),
        EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(128)'),
        EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)')
END;
  • Check the SQL Agent job history for the exact failure time
  • Cross-reference with your source control system if the procedure exists in version history
  • Review backup schedules - a database restore might have overwritten the procedure