Fixing SQL Server Case Sensitivity Issues with GitHub Copilot CLI

When creating dbatools commands, most of us use case-insensitive collation because that's the default and what we use in our labs. So creating commands that call T-SQL always works on our machines, but then we'll get an issue requesting that we fix a command to work on someone's case-sensitive SQL Server.

With case-sensitive collation (CS), dbo.cats and dbo.Cats are two distinct objects.

Fixing these SQL queries usually requires me to open the command, copy the T-SQL, paste it into SSMS (which would fix the casing), then copy it back to the command.

I'd long thought about setting up a test runner with case-sensitive collation, but it never happened. For projects like dbatools with T-SQL queries scattered across hundreds of files, manually fixing case mismatches is a chore.

It's a perfect task for AI, though, and I ended up trying 3 different approaches.

Get a reference list first

Before any AI can fix case sensitivity, it needs to know the correct casing. For projects using user or system tables that actually obey collation rules (like msdb..backupset), I exported all the canonical names and wrote them to disk:

 1$query = @"
 2SELECT DISTINCT
 3    SCHEMA_NAME(schema_id) + '.' + name AS FullName
 4FROM sys.objects
 5WHERE type IN ('U', 'V', 'P', 'FN', 'IF', 'TF')
 6ORDER BY FullName
 7"@
 8
 9Get-DbaDatabase -SqlInstance sql01 -Database master, msdb |
10    Invoke-DbaQuery -Query $query |
11    Select-Object -ExpandProperty FullName |
12    Out-File -FilePath ./sql-system-objects.txt

System catalogs like sys.databases or INFORMATION_SCHEMA.TABLES don't need to be included becuase they're hardcoded and always case-insensitive, but real tables in user databases (and msdb where most backup/restore info resides) definitely do. Exporting them gives the AI a solid reference for matching exact casing.

For your own custom app with your own database schemas, you'd run:

 1$query = @"
 2SELECT DISTINCT
 3    SCHEMA_NAME(schema_id) + '.' + name AS FullName
 4FROM sys.objects
 5WHERE type IN ('U', 'V', 'P', 'FN', 'IF', 'TF')
 6ORDER BY FullName
 7"@
 8
 9Get-DbaDatabase -SqlInstance sql01 -Database yourapp |
10    Invoke-DbaQuery -Query $query |
11    Select-Object -ExpandProperty FullName |
12    Out-File -FilePath ./myapp-sql-objects.txt

Find files that need fixing

Once you have your reference list, the next step is to locate all PowerShell files with SQL queries. This search finds files using .Query, .Execute, or Invoke-DbaQuery, the three ways we send T-SQL queries to SQL Server:

1Get-ChildItem -Path . -Filter *.ps1 -Recurse |
2    Where-Object FullName -notmatch '\\tests\\|\.github' |
3    Select-String -Pattern '\.(Query|Execute)|Invoke-DbaQuery' |
4    Select-Object -ExpandProperty Path -Unique

I excluded test files because we don't need to handle other people's collation within our test files. Maybe one day I'll fix those queries, but not today.

Testing three approaches

I tested three AI tools on the same task. Each got identical inputs: the files to fix, a detailed prompt explaining the requirements, and the reference list.

Claude Code with Sonnet: Too thorough

First attempt used Claude Code (my current go-to) with Sonnet 4.5. It spent over 2 minutes per file, carefully analyzing context, suggesting architectural improvements, and trying to understand why each query was written that way.

For a throwaway batch task, this was overkill. I needed case fixes, not code reviews. It did too much and, weirdly, made more mistakes than usual. This is one of the first tasks that Claude Code didn't handle well.

Claude Code is built for multi-file refactoring and architectural decisions—when there's real complexity to navigate, that's exactly what you want. But for mechanical, repetitive tasks where the instructions are crystal clear, all that context-gathering just adds friction.

Claude Code with Haiku: Too unintelligent

Next, I switched to Haiku for speed. It blazed through files in 20-40 seconds but replaced schemas that shouldn't be touched and replaced them with the wrong prefix.

The speed was great but the accuracy was not good enough.

GitHub Copilot CLI with Sonnet: Just right

GitHub Copilot CLI with Sonnet 4.5 nailed it. Exciting news for those of us who already have a subscription through work. GitHub Copilot CLI (not the gh CLI) with Sonnet spent about 20 seconds per file, made accurate changes, and didn't overthink it.

Why the difference? Near as I can figure, Copilot CLI is built for discrete changes while Claude Code is meant for sweeping work. Copilot CLI acts like a focused surgical tool—it takes your prompt, applies it to the specific file, and moves on. No attempt to solve bigger problems you didn't ask it to solve. For batch operations where you already know exactly what needs to happen, that constraint turns out to be a feature. Turns out, the tooling and environment might matter as much as the model for certain task types.

In the end, of 262 files that I piped in, there was only ONE mistake that I could find with the Copilot CLI changes! All of my tests passed except for the ones associated with that query. I don't have the attention span right now to be as detail oriented as I need to be for reviewing so I ended up just deleting that branch. But at a later date, I'll reapproach.

Running the fix

Using aitools, the actual command is straightforward:

1Get-ChildItem -Path . -Filter *.ps1 -Recurse |
2    Where-Object FullName -notmatch '\\tests\\' |
3    Select-String -Pattern '\.(Query|Execute)|Invoke-DbaQuery' |
4    Select-Object -ExpandProperty Path -Unique |
5    Invoke-AITool -Prompt .\tsql-case-sensitive.md -Context ./sql-system-objects.txt

The prompt file (tsql-case-sensitive.md), which I built with Claude's help, contained instructions like:

 1# T-SQL Case Sensitivity Fix
 2
 3Fix T-SQL casing in queries for case-sensitive collations.
 4
 5Reference the attached file which contains canonical casing for all SQL Server system objects.
 6
 7## Rules
 8
 91. **Keywords**: UPPERCASE (SELECT, FROM, WHERE, JOIN, INNER, LEFT, RIGHT, FULL, OUTER, CROSS, APPLY, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, TRUNCATE, MERGE, ORDER BY, GROUP BY, HAVING, CASE, WHEN, THEN, ELSE, END, AND, OR, NOT, IN, EXISTS, LIKE, BETWEEN, IS, NULL, AS, ON, WITH, OVER, PARTITION BY, ROW_NUMBER, RANK, DENSE_RANK, NTILE, UNION, EXCEPT, INTERSECT, ALL, DISTINCT, TOP, OFFSET, FETCH, BEGIN, END, IF, ELSE, WHILE, BREAK, CONTINUE, RETURN, DECLARE, SET, EXEC, EXECUTE, TRY, CATCH, THROW, RAISERROR, PRINT, GO, USE, INTO, VALUES, DEFAULT, CONSTRAINT, PRIMARY KEY, FOREIGN KEY, REFERENCES, CHECK, UNIQUE, INDEX, VIEW, PROCEDURE, FUNCTION, TRIGGER, SCHEMA, DATABASE, TABLE, COLUMN, GRANT, REVOKE, DENY)
10
112. **System objects**: Match exact casing from `sql-system-objects.txt` (e.g., `sys.databases`, `sys.dm_exec_sessions`, `INFORMATION_SCHEMA.TABLES`)
12
133. **Built-in functions**: UPPERCASE (ISNULL, COALESCE, CAST, CONVERT, COUNT, SUM, AVG, MIN, MAX, GETDATE, GETUTCDATE, SYSDATETIME, DATEADD, DATEDIFF, DATEPART, YEAR, MONTH, DAY, STRING_AGG, CONCAT, SUBSTRING, LEN, CHARINDEX, PATINDEX, REPLACE, UPPER, LOWER, LTRIM, RTRIM, TRIM, LEFT, RIGHT, REVERSE, STUFF, FORMAT, TRY_CAST, TRY_CONVERT, IIF, CHOOSE, ABS, CEILING, FLOOR, ROUND, POWER, SQRT, LOG, EXP, NEWID, RAND, CHECKSUM, HASHBYTES, ROW_NUMBER, RANK, DENSE_RANK, NTILE, LAG, LEAD, FIRST_VALUE, LAST_VALUE, CUME_DIST, PERCENT_RANK)

The context file (sql-system-objects.txt) provided the reference list of correct casings.

Keywords like SELECT and ALTER don't technically need fixing for case-sensitivity since they're language keywords, but I prefer consistency and it's easy enough to fix while I'm at it.

What I learned

GitHub Copilot CLI won because it generally does less. Claude Code kept trying to understand the broader context and improve things I didn't ask it to improve. Even when I put instructions in all caps at the start and end of the prompt: IF THERE'S NO WORK TO DO, DON'T INVENT WORK, it still rewrote some PowerShell. For a task where the instructions are "fix these specific things and nothing else," Claude Code seemed bored.

At least that weekend, anyway. Model and tool performance is known to be incredibly variable, week-to-week and maybe this week Claude Code's prompt adherance was weak.

If I needed to refactor how dbatools handles queries across the entire codebase, Claude Code would be the right tool. For batch-processing 262 files with one mechanical fix each, Copilot CLI's narrower focus was perfect. It did what I asked and stopped.

The reference list approach worked well enough that I'll use it for other batch SQL fixes. Export the correct schema, pipe files through with clear instructions, spot-check the results. One mistake in 262 files is acceptable for a first pass that I can clean up later.

Update 11 Nov: I tidied up my prompt and it made zero mistakes! After updating this blog post, these changes have been merged into dbatools 🤩

Seems that sometimes, the best tool is the one that stops when you tell it to stop.