Welcome to the lab. This is where your skills become job-ready.
You are going to build a stored procedure that looks and behaves like something a real DBA would ship
into production: validated inputs, safe transactions, guardrails, clean outputs, and evidence logging.
The goal is not just “make it work.” The goal is:
make it safe, predictable, and auditable.
Lab description: You will create a stored procedure that generates a customer order summary
and enforces professional safety rules. The procedure will validate inputs, prevent unsafe ranges,
use TRY/CATCH, return both an OUTPUT parameter and a RETURN code, and provide a clear evidence trail.
You will also test success paths and failure paths so you know exactly how it behaves under pressure.
This lab uses the AdventureWorks sample database.
If you do not have it installed yet, you can still follow the pattern in any practice database,
but AdventureWorks is recommended because the schema is realistic.
You will build this stored procedure:
dbo.usp_CustomerOrderSummary
@CustomerID int (required)@StartDate date (required)@EndDate date (required)@TopN int = 50 (optional, capped)@MaxAllowedDays int = 366 (guardrail)@ReturnedRows int OUTPUT (how many rows returned)@CustomerID is positive and exists.@StartDate and @EndDate are not NULL and StartDate <= EndDate.@MaxAllowedDays to avoid runaway queries.@TopN is between 1 and 500.@ReturnedRows as output and a return code.Why this is DBA-grade: This procedure protects the database and supports automation.
It is safe to call from jobs, dashboards, and applications.
CREATE OR ALTER PROCEDURE dbo.usp_CustomerOrderSummary
@CustomerID int,
@StartDate date,
@EndDate date,
@TopN int = 50,
@MaxAllowedDays int = 366,
@ReturnedRows int OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- Default OUTPUT value
SET @ReturnedRows = 0;
--------------------------------------------------------------------
-- Evidence header (useful for debugging/job logs)
--------------------------------------------------------------------
DECLARE @StartTime datetime2(3) = SYSDATETIME();
BEGIN TRY
----------------------------------------------------------------
-- 1) Parameter validation (fail fast)
----------------------------------------------------------------
IF @CustomerID IS NULL OR @CustomerID <= 0
RETURN 10; -- invalid customer id
IF @StartDate IS NULL OR @EndDate IS NULL
RETURN 11; -- missing dates
IF @StartDate > @EndDate
RETURN 12; -- invalid range
IF @TopN IS NULL OR @TopN < 1 OR @TopN > 500
RETURN 13; -- invalid TopN
IF @MaxAllowedDays IS NULL OR @MaxAllowedDays < 1 OR @MaxAllowedDays > 3660
RETURN 14; -- invalid guardrail
DECLARE @RangeDays int = DATEDIFF(DAY, @StartDate, @EndDate) + 1;
IF @RangeDays > @MaxAllowedDays
RETURN 15; -- range too large (safety stop)
----------------------------------------------------------------
-- 2) Verify customer exists (controlled failure)
----------------------------------------------------------------
IF NOT EXISTS (SELECT 1 FROM Sales.Customer WHERE CustomerID = @CustomerID)
RETURN 20; -- customer not found
----------------------------------------------------------------
-- 3) Query logic (SARGable date filter)
-- EndExclusive = EndDate + 1 day
----------------------------------------------------------------
DECLARE @EndExclusive date = DATEADD(DAY, 1, @EndDate);
-- Note: This is read-only, so we do not need an explicit transaction.
-- Transaction would be more appropriate for data changes (INSERT/UPDATE/DELETE).
-- DBAs keep read-only procedures lean and safe.
SELECT TOP (@TopN)
soh.SalesOrderID,
soh.OrderDate,
soh.TotalDue
FROM Sales.SalesOrderHeader AS soh
WHERE soh.CustomerID = @CustomerID
AND soh.OrderDate >= @StartDate
AND soh.OrderDate < @EndExclusive
ORDER BY soh.OrderDate DESC, soh.SalesOrderID DESC;
SET @ReturnedRows = @@ROWCOUNT;
----------------------------------------------------------------
-- 4) Controlled outcome rules
----------------------------------------------------------------
IF @ReturnedRows = 0
RETURN 30; -- no orders in date range (not an error, but controlled result)
RETURN 0; -- success
END TRY
BEGIN CATCH
----------------------------------------------------------------
-- Catch unexpected errors and return evidence
----------------------------------------------------------------
DECLARE @ErrNumber int = ERROR_NUMBER();
DECLARE @ErrSeverity int = ERROR_SEVERITY();
DECLARE @ErrState int = ERROR_STATE();
DECLARE @ErrLine int = ERROR_LINE();
DECLARE @ErrProc nvarchar(128) = ERROR_PROCEDURE();
DECLARE @ErrMsg nvarchar(4000) = ERROR_MESSAGE();
-- Evidence output (you can keep this for debugging or log it to a table later)
SELECT
@StartTime AS ScriptStartTime,
SYSDATETIME() AS ScriptEndTime,
@@SERVERNAME AS ServerName,
DB_NAME() AS DatabaseName,
SUSER_SNAME() AS ExecutedBy,
@CustomerID AS CustomerID,
@StartDate AS StartDate,
@EndDate AS EndDate,
@TopN AS TopN,
@MaxAllowedDays AS MaxAllowedDays,
@ReturnedRows AS ReturnedRows,
@ErrNumber AS ErrorNumber,
@ErrSeverity AS ErrorSeverity,
@ErrState AS ErrorState,
@ErrProc AS ErrorProcedure,
@ErrLine AS ErrorLine,
@ErrMsg AS ErrorMessage;
-- Fail loudly for unexpected errors (preferred in production jobs)
THROW;
END CATCH;
END;
DECLARE @Rows int;
DECLARE @RC int;
EXEC @RC = dbo.usp_CustomerOrderSummary
@CustomerID = 11000,
@StartDate = '2014-01-01',
@EndDate = '2014-12-31',
@TopN = 25,
@MaxAllowedDays = 366,
@ReturnedRows = @Rows OUTPUT;
SELECT
@RC AS ReturnCode,
@Rows AS ReturnedRows;
DECLARE @Rows int;
DECLARE @RC int;
EXEC @RC = dbo.usp_CustomerOrderSummary
@CustomerID = 999999, -- likely not present
@StartDate = '2014-01-01',
@EndDate = '2014-12-31',
@TopN = 25,
@MaxAllowedDays = 366,
@ReturnedRows = @Rows OUTPUT;
SELECT @RC AS ReturnCode, @Rows AS ReturnedRows;
DECLARE @Rows int;
DECLARE @RC int;
EXEC @RC = dbo.usp_CustomerOrderSummary
@CustomerID = 11000,
@StartDate = '2014-12-31',
@EndDate = '2014-01-01', -- invalid
@TopN = 25,
@MaxAllowedDays = 366,
@ReturnedRows = @Rows OUTPUT;
SELECT @RC AS ReturnCode, @Rows AS ReturnedRows;
DECLARE @Rows int;
DECLARE @RC int;
EXEC @RC = dbo.usp_CustomerOrderSummary
@CustomerID = 11000,
@StartDate = '2011-01-01',
@EndDate = '2014-12-31',
@TopN = 25,
@MaxAllowedDays = 60, -- deliberately strict guardrail
@ReturnedRows = @Rows OUTPUT;
SELECT @RC AS ReturnCode, @Rows AS ReturnedRows;
Document these codes in your DBA notebook:
DBA reason: This makes your procedure automation-friendly and easy to support.
Next, you will move into environment setup and repeatable installation discipline:
sizing basics, virtualization choices, Windows hardening, SQL Server installation strategy,
and building a reusable DBA install checklist.
Not a member yet? Register now
Are you a member? Login now