Comprehensive Deprecated Feature Scan with AI

Every SQL Server instance has a built-in tattletale: the SQLServer:Deprecated Features performance counter object. It quietly increments counters every time your workload uses something Microsoft plans to remove. Most DBAs never look at it — and then act surprised when an upgrade breaks something.

Static code scanning catches deprecated syntax sitting in stored procedures and views. Performance counters catch deprecated features used at runtime — in application queries, dynamic SQL, and ad-hoc batches that no code scan will ever find. You need both. An AI agent can help you build a comprehensive scan that covers the full surface area.

The Problem

You’re planning an upgrade from SQL Server 2017 (compatibility level 140) to SQL Server 2025 (compatibility level 170). You need to find every deprecated feature your databases use — not just the ones sitting in sys.sql_modules, but the ones hiding in cached plans, runtime workloads, and application-side SQL. You need the results prioritized so you know what’s a ticking time bomb and what’s a cosmetic warning.

The Prompt

What the Agent Produced

The agent returned three distinct scripts — one for each detection layer. The runtime counter query was clean and immediately useful. The cached plan scan used sys.dm_exec_query_plan with CROSS APPLY to extract plan XML and sys.dm_exec_sql_text to get the source SQL. The static code scan used pattern matching against sys.sql_modules definitions.

The severity classification was reasonable: non-ANSI outer joins and COMPUTE BY were marked as high priority (these have been removed in recent versions), while deprecated system table references like sysprocesses were correctly classified as lower priority since the compatibility views still work.

What I Validated and Changed

The agent’s runtime counter query worked as written, but I made several adjustments across the three scripts:

  • The cached plan scan was too aggressive. It was flagging comments containing *= as deprecated join syntax. I added pattern refinements to reduce false positives — checking for whitespace or column-name characters on either side of the operator.
  • The SET ROWCOUNT detection needed context. SET ROWCOUNT 0 (turning it off) isn’t deprecated — only SET ROWCOUNT n before a DML statement is. The agent was flagging every occurrence. I adjusted the pattern to exclude SET ROWCOUNT 0.
  • Numbered stored procedures need a specific pattern. The agent searched for ;1 and ;2 in procedure names, but that also matches semicolons used as statement terminators. I changed the pattern to look for CREATE PROC[EDURE] followed by a name with ; and a digit.
  • Added sys.server_sql_modules for server-scoped objects — logon triggers and DDL triggers at the server level that the database-scoped scan misses entirely.

The Final Scripts

Runtime Deprecated Feature Detection

This is the single most valuable query in the toolkit. If a counter has a high value, that feature is being actively used in production — by application code, Agent jobs, or ad-hoc queries that no static scan will find. Let it run for at least a full business cycle (ideally a week covering month-end processing) before drawing conclusions.

Cached Plan Deprecated Syntax Scan

Static Code Deprecated Construct Scan

Don’t forget server-scoped objects:

Building the Remediation Plan

Once you have your findings, ask the agent to prioritize the fixes:

The agent maps each deprecated construct to its replacement: *= becomes LEFT OUTER JOIN, COMPUTE BY becomes window functions with GROUP BY or application-layer subtotals, text/ntext/image become varchar(max)/nvarchar(max)/varbinary(max), sysprocesses becomes sys.dm_exec_sessions joined to sys.dm_exec_requests, and string RAISERROR becomes the parenthesized form with severity and state parameters.

The remediation effort classifications are useful for project planning. Replacing sysprocesses with DMVs is usually trivial — the column names are similar. Replacing COMPUTE BY can be complex if downstream reports depend on the extra result sets it produces. Non-ANSI join conversion is moderate — the logic is equivalent, but you need to be careful with multi-table chains where the precedence of *= differs from explicit LEFT JOIN ordering.

Try This Yourself

Start with the runtime counter query. Run it on your busiest production instance and look at the top five deprecated features by usage count. Those are the ones your workload is actively using — not hypothetical risks buried in unused procedures, but real runtime behavior. Then run the static scan on one database and compare: some of your highest-count runtime features may not appear in sys.sql_modules at all, because they’re coming from application-side queries.

The gap between runtime counters and static code analysis is where the real upgrade risk lives. For the full migration planning workflow — including compatibility level assessment, breaking change analysis, and runbook generation — see Post 11: Migration Planning.


Part of the ALTER DBA ADD AGENT series.