CtrlK
BlogDocsLog inGet started
Tessl Logo

alonso-skills/sql-writing-guidelines

Use when writing or reviewing T-SQL, creating stored procedures, designing table schemas, writing views, building migrations, defining custom types, or architecting a SQL Server application database. Also use when writing RAISERROR patterns, CHECK constraints with scalar functions, base/subtype table hierarchies, composite key designs, role-scoped views with row-level security, or idempotent DDL scripts. If you are touching SQL for an application database, use this skill.

90

Quality

90%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

error-handling.mdreferences/

Error Handling

Procedures use GOTO-based error flow with structured error codes. Every failure has an explicit, labeled exit path — no hidden control flow, no exception swallowing.

Table of Contents

  • Philosophy
  • DML Error Checking
  • Structured Error Codes
  • RAISERROR Patterns

Philosophy

Procedures only perform deterministic, local operations — table reads, writes, and checks fully within the database's control. These operations don't "try" to succeed; they either do or they don't, and we check the result immediately with @@ROWCOUNT and @@ERROR.

TRY-CATCH is reserved exclusively for non-deterministic operations — like an HTTP request from SQL Server that depends on a network service. Even then, it's discouraged. If there is a failure, exit early. Don't swallow errors, don't retry silently, don't mask what happened.

GOTO gives you explicit, visible control flow. Every error path is a labeled jump target. There's no hidden control flow, no ambiguity about what gets rolled back. The procedure reads top-to-bottom with clear exit points.


DML Error Checking

After every DML statement, immediately capture and check:

SELECT @RowCnt = @@ROWCOUNT, @ErrNo = @@ERROR;

IF (@ErrNo <> 0) GOTO EXIT_TRANSACTION;
IF (@RowCnt = 0) BEGIN
    RAISERROR(50005, 16, 1, 'CloseAccount_trx: Account');
    GOTO EXIT_TRANSACTION;
END

@@ROWCOUNT and @@ERROR are reset by any statement — including SET, which resets @@ERROR to 0 on success. A single SELECT captures both atomically before either is lost.

Row count expectations by operation:

  • INSERT single row: expect @RowCnt = 1, error with 50004 (EXIT_NOT_ADDED)
  • UPDATE: expect @RowCnt > 0 (or = 1 for single-row), error with 50005 (EXIT_NOT_MODIFIED)
  • DELETE: expect @RowCnt > 0 (or = 1 for single-row), error with 50006 (EXIT_NOT_REMOVED)

Structured Error Codes

A catalog of semantic error codes registered via sp_addmessage. These are designed to be parsable by client applications — each error explicitly names what went wrong so upstream code can match on the error number and present meaningful feedback without guessing.

CodeNameMessage pattern
50001EXIT_ERRORAn error occurred (%s). %s
50002EXIT_NO_DATANo data was provided to %s. %s
50003EXIT_NOT_FOUNDA required record %s was not found. %s
50004EXIT_NOT_ADDEDNo data was added into %s when intended. %s
50005EXIT_NOT_MODIFIEDNothing was modified in %s when intended. %s
50006EXIT_NOT_REMOVEDNothing was removed from %s when intended. %s
50007EXIT_CANT_ADDCannot add %s. %s
50008EXIT_CANT_MODIFYCannot modify %s. %s
50009EXIT_CANT_REMOVECannot remove %s. %s
50010EXIT_BAD_DATABad data, %s. %s
50011EXIT_MODIFIED_ELSEWHERE%s was modified elsewhere. %s
50012EXIT_TRANCOUNTCannot run %s inside of an open transaction. %s
50013EXIT_NO_TRANCOUNTCannot run %s outside of an open transaction. %s
50014EXIT_PERMISSIONUser does not have permission on %s. (%s %s %s)

The first %s is always the procedure name (with optional context after a colon). The second %s is an optional variable for additional detail.


RAISERROR Patterns

-- Simple: just the procedure name
RAISERROR(50002, 16, 1, 'TransferFunds_trx');

-- With context: procedure name + what's missing
RAISERROR(50002, 16, 1, 'TransferFunds_trx: FromAccountNo');

-- With variable: procedure name + dynamic detail
RAISERROR(50005, 16, 1, 'TransferFunds_trx: Account', @AccountNo);

These errors are not project-specific — they are a reusable catalog. Build yours to match your system's failure modes, but the principle holds: every error should have a number, a name, and a structured message that both humans and code can consume.


See Also

  • Procedure Structure — the _trx / _utx templates where GOTO error flow and DML checks are used

references

application-settings.md

basetype-subtype.md

error-handling.md

hierarchical-keys.md

migration-patterns.md

naming-conventions.md

normal-form-violations.md

procedure-structure.md

query-patterns.md

relational-queues.md

security-permissions.md

view-patterns.md

SKILL.md

tile.json