Dissecting Dynamic SQL with AI

Every SQL Server estate has at least one stored procedure that builds its query as a string. You open it, see DECLARE @SQL nvarchar(MAX) on line 3, and spend the next hour mentally tracing which IF blocks append which WHERE clauses. Dynamic SQL isn’t inherently bad — sp_executesql with proper parameterization is a legitimate pattern — but the legacy version, built from raw string concatenation with no parameterization, is a different animal entirely.

AI agents are exceptionally good at untangling these procedures. They can trace variable assignments across hundreds of lines, reconstruct the final SQL for different parameter combinations, and flag injection risks that a tired DBA might miss on line 247 of a 300-line procedure. This is one of the places where AI saves the most time in reverse-engineering legacy code.

The Problem: A Kitchen-Sink Search Procedure

Here’s a pattern that exists in nearly every line-of-business application — a search procedure that builds its query dynamically based on which parameters the caller passes in. This is a simplified version of what I see in production:

At first glance this looks straightforward. But there are multiple problems hiding in this code that are hard to spot when you’re tracing the concatenation manually — especially in the production version, which usually has 30+ parameters and 400+ lines.

The Prompt

What the Agent Produced

The agent immediately identified three injection vectors:

  1. @CustomerName — directly concatenated into a LIKE clause. An input of '; DROP TABLE dbo.Orders; -- would execute arbitrary SQL. This is the primary injection vector.
  2. @StatusCode — directly embedded in quotes with string concatenation. It’s char(1), which limits the practical attack surface to a single character, but the pattern is still dangerous — if someone later widens this to varchar(20), it becomes exploitable. Bad habits baked into code survive longer than anyone expects.
  3. @SortColumn and @SortDirection — the existing procedure constrains these to known literals via IF/ELSE logic, so they’re not injectable as written. But the agent correctly flagged the pattern as risky: the safety depends on the whitelist being maintained as new sort options are added. The rewrite should use QUOTENAME or an explicit whitelist to make the safety structural rather than incidental.

The agent rendered the final SQL for the sample call, making the concatenation result visible:

Then it produced the parameterized rewrite:

What I Validated and Changed

The agent’s rewrite was solid. A few things I checked:

  1. The LIKE pattern. The agent parameterized the search value but kept the wildcard concatenation inside the dynamic SQL. This is correct — you can’t parameterize the % wildcards themselves, but the user input is now safely passed through sp_executesql‘s parameter binding. No injection risk.

  2. The ORDER BY whitelist. The CASE expression only emits column references from a fixed set of known values. Any unexpected input falls through to the ELSE (OrderDate). This is the correct pattern — you cannot parameterize ORDER BY columns, so whitelisting is the safe alternative.

  3. nvarchar vs varchar. The agent correctly switched to nvarchar(MAX) for the SQL string and parameter definition — sp_executesql requires Unicode strings. The original used varchar, which would cause a silent conversion or an error.

  4. Plan cache behavior. The parameterized version generates reusable query plans. The original EXEC() version creates a new plan for every distinct SQL string — which means every unique parameter combination compiles a new plan, bloating the plan cache. This is a significant performance improvement beyond the security fix.

One thing I added that the agent didn’t suggest: a PRINT @SQL debug mode controlled by a @Debug bit = 0 parameter. When legacy dynamic SQL misbehaves, being able to see the rendered SQL is invaluable for troubleshooting.

The Bigger Pattern

Dynamic SQL dissection is where AI agents earn their keep in legacy code work. The mechanical task — tracing string concatenation across dozens of IF blocks and variable assignments — is exactly what AI is good at. It doesn’t lose its place on line 183. It doesn’t accidentally skip a branch. It can reconstruct the final SQL for any combination of inputs instantly.

What the agent can’t do is tell you whether the query is correct for your business. It can tell you what the SQL does; it can’t tell you whether the results are what the users expect. That’s where you combine the agent’s technical analysis with conversations with the people who use the application. For the full approach to writing and refactoring T-SQL with AI, including testing strategies, see the earlier post in this series.

Try This Yourself

Find a dynamic SQL procedure in your environment — sp_helptext across your databases will turn up plenty. Feed it to the agent with this prompt:

Run both versions on a test system with identical parameters and compare the results. Pay special attention to edge cases — NULL parameters, empty strings, and values containing single quotes. The parameterized version should handle all of these safely; the original may not.

For the full legacy code reverse-engineering workflow, see Reverse-Engineering Legacy Stored Procedures.


Part of the ALTER DBA ADD AGENT series.