Build a Production-Style SQL Server Backend: A Capstone Ticket System Project

Written by

in

Build a Production-Style SQL Server Backend: A Capstone Ticket System Project

This combines every chapter of the advanced track into one realistic deliverable: the backend for TicketDesk, a small support-ticket system, built the way a real backend actually gets built — ambiguous edges, several defensible designs, and a requirement to justify your choices, not just produce code that runs.

TicketDesk: everything, one schema(each requirement maps to a chapter)Customercreates a ticketusp_Ticket_Create (Ch.1, Ch.4)Ticketstatus, priorityusp_Ticket_Assign (Ch.4)Agentmust be activeTicketAuditAFTER UPDATE trigger,set-based (Ch.7)TicketCommentauthor_type, bodyvw_AgentWorkloadmind the JOIN type — 0-ticket agents must still show (Ch.6)one schema, everychapter of the course ✓the app’s service account:least-privilege, neverdb_owner (Ch.10) 📌

Schema Requirements

  • Agent: agent_id, name, email (unique), is_active
  • Customer: customer_id, name, email (unique)
  • Ticket: ticket_id, customer_id (FK), assigned_agent_id (FK, nullable — unassigned tickets are a valid state), status, priority, created_at, resolved_at
  • TicketComment: comment_id, ticket_id (FK), author_type, body, created_at
  • TicketAudit: populated automatically by a trigger — old_status, new_status, changed_at

The Architecture, Visualized

Customer Ticket Agent TicketAudit TicketComment

Business Logic Requirements — Mapped to Where You Learned Each One

Requirement Chapter it draws on
fn_GetOpenTicketCount(@agentId) — scalar or inline TVF, with a justification comment for which type you chose and why Ch.2
usp_Ticket_Create — TRY/CATCH + transaction, OUTPUT parameter for the new ticket_id Ch.1, Ch.4
usp_Ticket_Assign — THROWs if the agent is not active Ch.4
usp_Ticket_Resolve — THROWs if the ticket is already closed Ch.4, Ch.5
AFTER UPDATE trigger on Ticket — logs every status change to TicketAudit, correctly set-based for multi-row updates Ch.7
vw_AgentWorkload — one row per active agent, including agents with zero open tickets (mind the JOIN type) Ch.6
-- A skeleton for one requirement, deliberately incomplete — you decide the JOIN type
CREATE VIEW dbo.vw_AgentWorkload AS
SELECT a.agent_id, a.name, COUNT(t.ticket_id) AS open_ticket_count
FROM dbo.Agent a
-- ??? JOIN dbo.Ticket t ON t.assigned_agent_id = a.agent_id AND t.status IN ('open','in_progress')
WHERE a.is_active = 1
GROUP BY a.agent_id, a.name;

The blank above is deliberate: pick the wrong JOIN type here and agents with zero open tickets silently vanish from the report — the exact LEFT JOIN + WHERE-vs-ON distinction from the Fundamentals course, now applied inside a view that a real dashboard would depend on.

Performance & Security Requirements

  • Populate Ticket with 5,000+ rows and design a covering/filtered index for “open tickets by agent, ordered by priority” — prove it with before/after STATISTICS IO (Ch.8)
  • Create a least-privilege service account for the application — not db_owner, with explicit GRANTs you can justify one by one (Ch.10)

Self-Check Before You Consider It Done

Check Why it matters
Run a multi-row UPDATE against Ticket’s status column and confirm every changed row appears in TicketAudit Catches the single-row-assumption trigger bug from Ch.7
Call usp_Ticket_Assign against an inactive agent Confirms your THROW logic actually fires, not just compiles
Query vw_AgentWorkload and confirm an agent with zero tickets still appears, with count 0 Confirms the correct JOIN type from the skeleton above
Log in as your least-privilege service account and confirm it genuinely cannot do more than granted The only real proof least-privilege was actually applied, not just declared

Why This Is the Right Capstone

Every chapter of this track shows up here: functions, procedures with proper error handling, a correctly set-based trigger, a view with the right JOIN type, indexing backed by real measurement, and least-privilege security. It mirrors how a real backend ticket actually gets built — ambiguous edges, multiple valid designs, and a requirement to justify your decisions, not just produce working code.

What comes next: Combined with the Fundamentals capstone, you now have two complete, defensible schemas behind you — a good portfolio starting point. The Performance Tuning course picks up exactly where this leaves off: given a schema like TicketDesk under real load, how do you diagnose and fix what’s actually slow, using evidence rather than guesswork.

Enjoyed this?

Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

You’ve completed the full curriculum! Check out SQL Server Fundamentals and SQL Server for Developers & DBAs, coming soon as structured courses on this site.