Teaching GitHub Copilot Your T-SQL Coding Standards

If you’re a DBA or database developer using GitHub Copilot CLI, you’ve probably noticed it generates functional T-SQL — but not necessarily T-SQL that matches your team’s coding standards. After several weeks of working with Copilot on real-world stored procedure development, I’ve built up a comprehensive instructions file that teaches it how I want my code written. I will update this post periodically to ensure it represents my then-current instructions.

Here’s what I’ve learned, and the rules I’ve established.

Where the Instructions Live

GitHub Copilot CLI reads a global instructions file from ~/.copilot/copilot-instructions.md. This is a plain markdown file that persists across sessions. Every time Copilot starts a new conversation, it reads this file and applies the rules to all code it generates.

The beauty of this approach is that rules accumulate organically. Each time Copilot generates something that doesn’t match your standards, you correct it once, add the rule to the instructions file, and never correct it again. You can even ask Copilot to update the global instructions file with specific choices you want it to be aware of.

The instructions I have so far include the following (there is a single code-block at the end you can copy-and-paste into your own file for convenience, or you can take bits and pieces you like).

The Rules

Semicolon Termination

Every T-SQL statement must be terminated with a semicolon. Microsoft has deprecated unterminated statements, and it’s only a matter of time before they start enforcing it. Copilot’s default behavior is to omit semicolons — this rule fixes that immediately.

Lowercase Data Types

Data types should always be lowercase. This is a stylistic choice, but it’s consistent with how SQL Server displays them in metadata views and INFORMATION_SCHEMA.

Always Specify varchar/nvarchar Size

Never use bare varchar or nvarchar without an explicit length. When converting an int to a string, use varchar(11) (enough for the full range including the negative sign). This prevents the silent truncation to varchar(1) or varchar(30) that catches people off guard.

Unicode String Prefix

Use the N'' prefix for all string literals assigned to or compared with nvarchar variables or columns. Without it, you risk silent data loss for any characters outside the code page.

COALESCE Over ISNULL

Use COALESCE() instead of ISNULL(). It’s ANSI standard, supports multiple arguments, and doesn’t silently constrain the return type to the first argument’s type the way ISNULL does.

CONVERT Over CAST

Use CONVERT() instead of CAST(), and include an explicit style parameter where it makes sense. CONVERT is more flexible, and the style parameter makes date/time formatting intentions explicit rather than relying on server defaults.

COUNT(1) Over COUNT(*)

Use COUNT(1) instead of COUNT(*). While modern query optimizers treat them identically, COUNT(1) makes the intent explicit — you’re counting rows, not evaluating columns.

Square Brackets on All Identifiers

Every column name, table name, schema name, and object name gets [square brackets]. No exceptions. This prevents collisions with reserved words and makes it immediately clear what’s an identifier versus a keyword.

BEGIN…END on All IF Statements

Every IF statement must use BEGIN...END, even for single-line bodies. This prevents the class of bugs where someone adds a second line to an IF block and doesn’t realize it’s not actually inside the conditional.

SQL Comment Style

SQL comments shall always follow the /* .... */ pattern; never use the -- style. If existing code uses -- style comments, convert them to /* ... */ when modifying the file.

Clause-Per-Line Formatting

Each major SQL clause starts on its own line, with arguments indented beneath it. This makes diffs cleaner and makes it easy to scan the structure of a query.

Stored Procedure Parameters

Always wrap the parameter list in parentheses on separate lines:

Variable Alignment

When declaring or setting multiple variables in a block, align the variable names, data types, and = signs into neat columns using spaces. Never use tabs.

CREATE TABLE Formatting

Opening parenthesis on its own line, each column indented, closing parenthesis on its own line:

Subqueries in Expressions

Expand subqueries onto multiple lines, just like standalone statements:

No Boilerplate or Commented-Out Code

Don’t leave behind template comments, copyright blocks, or commented-out lines like -- SELECT @sp_return. Keep the code clean. If it’s not executing, it shouldn’t be in the file.

Behavioral Rules

Context-Sensitive Transaction Handling

For databases where snapshot isolation is enabled, stored procedures should check @@TRANCOUNT at entry. If no transaction exists, open one and track ownership with a flag. On success, only COMMIT if we own the transaction. On error, only ROLLBACK if we own the transaction and @@TRANCOUNT > 0.

This keeps the proc safe when called standalone while being a good citizen inside a caller’s transaction (e.g., when an application wraps the call in a TransactionScope).

SNAPSHOT Isolation Over NOLOCK

Prefer SET TRANSACTION ISOLATION LEVEL SNAPSHOT over WITH (NOLOCK) in stored procedures. NOLOCK gives you dirty reads, phantom reads, and can even return duplicate rows or skip rows entirely during page splits. SNAPSHOT isolation gives you a consistent point-in-time view without any of those hazards.

There’s a critical caveat: SQL Server will abort and roll back the entire transaction if you attempt to switch to SNAPSHOT isolation inside an active transaction that started under a different isolation level. Per Microsoft’s documentation:

“With one exception, you can switch from one isolation level to another at any time during a transaction. The exception occurs when changing from any isolation level to SNAPSHOT isolation. Doing this causes the transaction to fail and roll back.”

This means you must guard the change with @@TRANCOUNT = 0:

Also worth noting: when SET TRANSACTION ISOLATION LEVEL is issued inside a stored procedure, the isolation level automatically reverts to the caller’s level when the proc returns. The reset to READ COMMITTED at the end is a defensive measure for clarity, not strictly required.

Caution for write procedures: SNAPSHOT isolation is not universally safe for procedures that write data. Procedures that reference key-tables, counter-tables, sequence-tables, or any pattern where a column is updated using ROWLOCK, UPDLOCK, or other specific locking semantics may be negatively affected. SNAPSHOT isolation can mask concurrent updates and introduce race conditions in these scenarios. Do not impose SNAPSHOT isolation on write procedures without in-depth analysis and testing.

OUTPUT Clause Over @@ROWCOUNT

Prefer using the OUTPUT clause with a temp table to capture affected rows, rather than relying on @@ROWCOUNT. The OUTPUT clause gives you the actual data that was modified, which is far more useful for logging, auditing, and conditional logic.

The Payoff

After establishing these rules, every stored procedure Copilot generates comes out looking like it was written by someone who actually cares about the codebase. The formatting is consistent, the patterns are defensive, and the code is clean enough to pass a code review without a round-trip.

The key insight is that Copilot’s instructions file is a living document. Every time you see something you don’t like in the generated code, don’t just fix it — add the rule. Over a few weeks of active development, you’ll build up a comprehensive style guide that’s enforced automatically.

The instructions file doesn’t just help Copilot — it helps you crystallize what your standards actually are. Half of the rules in my file were things I’d been doing instinctively for years but had never written down. The act of writing them as explicit instructions forced me to think about why I do things a certain way, and in a few cases, I refined my own practices in the process.

Let me know in the comments if you’ve built up your own Copilot instructions for T-SQL, or if you have rules you think I should add to mine.

The Full Instructions File

Here’s what a complete ~/.copilot/copilot-instructions.md looks like, incorporating all of the rules above. Copy it, tweak it to match your team’s preferences, and start building on it.