Welcome back. In production, SQL scripts are not judged by how they behave when everything goes right.
They are judged by how safely they behave when something goes wrong.
DBAs write scripts that must survive real-world conditions:
blocking, deadlocks, permission issues, missing objects, timeouts, data surprises, and partial failures.
Your job is to build scripts that fail in a controlled way, leave the database consistent, and leave evidence
for troubleshooting.
Lesson description: You will learn SQL Server’s TRY/CATCH syntax, what errors can and cannot be caught,
how to capture useful error details, how to combine TRY/CATCH with transactions for safe rollback,
how to implement “pre-flight” validation checks (defensive scripting),
how to avoid partial changes, how to log outcomes, and how DBAs structure scripts so they are repeatable, auditable,
and safe to run under pressure.
TRY/CATCH in T-SQL allows you to:
BEGIN TRY ... END TRYBEGIN CATCH ... END CATCHTRY/CATCH does not magically prevent all failures. Your goal is:
fail safely, fail clearly, and leave evidence.
BEGIN TRY
-- Work goes here
END TRY
BEGIN CATCH
-- Error handling goes here
END CATCH;
Inside the CATCH block, SQL Server exposes functions that tell you what happened.
ERROR_NUMBER() — error codeERROR_MESSAGE() — text messageERROR_SEVERITY() — severity levelERROR_STATE() — state (more detail)ERROR_LINE() — line number where error occurredERROR_PROCEDURE() — stored procedure name (if applicable)DBAs capture all useful metadata and return it consistently. This pattern is job-ready.
BEGIN TRY
-- Example: harmless query
SELECT TOP (1) name FROM sys.databases;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
DBA reason: If a SQL Agent job fails, you want the log to show exact error details, not “it failed.”
A defensive DBA script verifies assumptions before it runs. This prevents messy partial failures.
Examples of assumptions:
DECLARE @DbName sysname = DB_NAME();
IF DATABASEPROPERTYEX(@DbName, 'Status') <> 'ONLINE'
BEGIN
THROW 50001, 'Database is not ONLINE. Aborting script.', 1;
END;
DBA note: THROW stops execution cleanly and can be caught by TRY/CATCH.
Both exist, but for modern SQL Server scripting:
THROW for re-throwing errors and raising custom errors simply.RAISERROR is older; you will still see it in legacy scripts.IF 1 = 1
BEGIN
THROW 50002, 'Pre-check failed: required condition not met.', 1;
END;
This preserves original error info for logs/callers.
BEGIN TRY
-- Force an error (demo)
SELECT 1 / 0;
END TRY
BEGIN CATCH
-- Log details first
SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
-- Re-throw original error
THROW;
END CATCH;
The most important defensive DBA pattern is:
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN;
-- Work goes here (INSERT/UPDATE/DELETE or schema changes)
-- Example: placeholder query
SELECT TOP (1) * FROM sys.objects;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRAN;
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage,
ERROR_LINE() AS ErrorLine,
XACT_STATE() AS XactState;
THROW; -- optional: bubble error up to caller/job
END CATCH;
0 = no active transaction1 = active transaction, can commit-1 = active transaction but uncommittable (must rollback)DBA rule: If a CATCH happens and XACT_STATE is not 0, rollback.
DBAs protect production by adding guardrails to prevent accidental mass updates/deletes.
A job-ready script validates row counts before committing.
This is a pattern you will use often:
DECLARE @MaxAllowedRows int = 100; -- guardrail
DECLARE @RowsAffected int;
BEGIN TRY
BEGIN TRAN;
-- Example only: pretend update
-- UPDATE dbo.SomeTable SET ... WHERE ...
-- SET @RowsAffected = @@ROWCOUNT;
SET @RowsAffected = 150; -- demo
IF @RowsAffected > @MaxAllowedRows
BEGIN
THROW 50003, 'Safety stop: too many rows would be affected. Aborting.', 1;
END;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRAN;
SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
DBA mindset: Production safety > “getting it done quickly.”
Professional DBA scripts include evidence:
DECLARE @StartTime datetime2(3) = SYSDATETIME();
SELECT
@StartTime AS ScriptStartTime,
SUSER_SNAME() AS ExecutedBy,
@@SERVERNAME AS ServerName,
DB_NAME() AS DatabaseName,
APP_NAME() AS AppName,
HOST_NAME() AS HostName;
SELECT
SYSDATETIME() AS ScriptEndTime,
DATEDIFF(SECOND, @StartTime, SYSDATETIME()) AS DurationSeconds;
DBA value: This makes your work auditable and easy to troubleshoot later.
Save each as a script in your “DBA script pack.”
Next, you will go deeper into transaction behavior: isolation, nested transactions,
how locks appear, what “uncommittable” means, and the exact patterns DBAs use to protect production changes.
Not a member yet? Register now
Are you a member? Login now