Blog Posts

WISdom + Claude: Use AI to Analyze SQL Server Telemetry

Every Monday, somebody asks the DBA team a version of the same question: what got more expensive last week, and was it the deploy?

Dashboards are good at now. They show you the current top queries, the current waits, the current hot server. The Monday question is about deltas: which statements cost more this week than last, whether that is volume or cost per execution, and whether the change lines up with something your team shipped. Answering it by hand means exporting two weeks of data, joining them in a spreadsheet, and eyeballing.

WISdom already computes the input that makes this question answerable at all: the dollar cost of every statement, the number you hand Finance instead of metrics they can’t read. And WISdom Connect, our REST API, hands that data to anything you want to build with it. In this post we’ll wire it to Claude and build a weekly cost regression brief: a short, plain-English report that names the movers, does the per-execution math, checks your logged environment events for a likely cause, and lands in your inbox or Slack before standup.

This isn’t a dashboard WISdom ships today. That’s the point. WISdom Connect exists so the views your team needs but we haven’t built yet are an afternoon away, not a feature request away.

You do not need to be a developer to follow this. You will not write any code. Claude writes the script, runs it, and hands you the report. The setup section below assumes you have never created a .env file or opened a terminal, and it says exactly what to type. If you are already fluent, skip ahead to “The query that does the heavy lifting.”

What you’ll build

  • A script (Claude writes it) that pulls the top 50 statements by dollar cost for the last 7 days and the 7 days before that, plus a second pair of calls at database grain
  • A diff keyed on statement hash: new entrants, risers, droppers, flat, and a separate “exited the top 50” bucket so absence never gets miscounted as improvement
  • Per-riser math that splits every cost change into volume vs cost per execution, with an assert that the split sums exactly to the delta
  • A correlation pass against your environment events (deploys, config changes, maintenance), reported as timing, not causation
  • A brief in markdown plus a self-contained HTML version, led by a waterfall chart that bridges last week’s total to this week’s with no plug figure
  • A schedule, so it runs every Monday without you

Time to first brief: about 30 minutes, most of it one-time setup.

What you need

  • A WISdom account with at least two weeks of collection history. The 30-day free trial works, no credit card.
  • An API key from the Integration screen in WISdom. We generate it in step 1.
  • An AI coding assistant. We used Claude Code, the command-line version of Claude, and step 2 installs it. Nothing here is Claude-specific though: the prompt is plain English and the API publishes an OpenAPI spec, so Codex, Cursor, OpenCode, Copilot or Aider will follow the same steps. Use whichever you already have open. The one thing that matters is that it can write and run a file on your own machine, which is what makes this 30 minutes instead of an afternoon.
  • Python 3, because the script gets written in Python. You will not write any of it. Step 2 checks whether you already have it.

Setup, from zero

Four steps, once. If you already have Claude Code and somewhere you keep API keys, this takes three minutes.

1. Generate your WISdom API key

In WISdom, open the Integration screen and generate an API key. Copy the secret it shows you and keep the tab open until you have pasted it somewhere safe. It is shown once.

Two things to know about the key. It is scoped to the account it was generated in, which is the least-privilege behavior you want: one key sees one customer account. And it reads real statement text and inventory from your environment, so treat it like any production credential. It belongs in a secret store or a .env file, never in a chat message, never in a screenshot, never committed to a repo.

2. Install Claude Code, and check for Python

Claude Code is Claude in your terminal. It can read files, write a script, run it, and read the error if the script fails, which is what makes this a 30-minute project instead of a 300-line one.

Open a terminal. On Windows, press Start, type PowerShell, and open Windows PowerShell. On a Mac, press Cmd+Space, type Terminal, and press Return. A terminal is just a window where you type commands instead of clicking.

Install Claude Code. This needs Node.js, so if the command comes back “not recognized,” install Node from nodejs.org first and open a fresh terminal:

npm install -g @anthropic-ai/claude-code

Using a different assistant? Skip this install. Everything from here works the same in Codex, Cursor, OpenCode, Copilot, Aider, or whatever you already have open. Carry on with the Python check below, and wherever the steps say to start claude, start your own tool instead.

Check whether you already have Python:

python --version

A version number of 3.9 or higher means you are set. If you get an error, or the Microsoft Store opens, install Python from python.org, and on Windows tick “Add python.exe to PATH” on the first screen of the installer. Open a fresh terminal afterward.

3. Put the key in a .env file

A .env file is a plain text file that holds secrets as NAME=value, one per line. Nothing more exotic than that. The name is literally a dot followed by “env”, with no other name and no .txt on the end, and programs read values out of it at the moment they run. It exists so your key lives in one place you control instead of being pasted into a script, a prompt, or a chat window where it would sit forever.

We will put it in your home folder, which is C:\Users\<you> on Windows and /Users/<you> on a Mac.

Windows (PowerShell):

Add-Content -Path "$env:USERPROFILE\.env" -Value "WISDOM_CONNECT_API_KEY=your_api_secret_here"

macOS or Linux:

echo 'WISDOM_CONNECT_API_KEY=your_api_secret_here' >> ~/.env

Replace your_api_secret_here with the secret you copied in step 1. No quotes around it, no spaces either side of the =.

A plain environment variable works too, but a file survives shell restarts and the long-lived sessions where an exported variable quietly goes stale.

4. Check that the key works

The fastest check is to let Claude do it. In your terminal, move to an empty folder and start Claude Code:

claude

Then ask it, in plain English:

My WISdom Connect API key is in a .env file in my home directory as
WISDOM_CONNECT_API_KEY. Call GET https://api.fortifiedwisdom.com/v1/accounts with
the header "Authorization: X-API-KEY <key>" and an explicit User-Agent, and tell me
which accounts come back. Read the key from the file at runtime, never print it.

You should get your own account name back. The key I am using here is scoped to our demo account, so mine comes back as WISdom Demo #1; yours will say whatever your account is called. To verify it by hand instead, the equivalent one-liner is:

Windows (PowerShell):

$key = (Get-Content "$env:USERPROFILE\.env" | Select-String '^WISDOM_CONNECT_API_KEY=').ToString().Split('=',2)[1]
Invoke-RestMethod -Uri "https://api.fortifiedwisdom.com/v1/accounts" -Headers @{ Authorization = "X-API-KEY $key"; "User-Agent" = "wisdom-brief/1.0" }

macOS or Linux:

curl -s "https://api.fortifiedwisdom.com/v1/accounts" \
  -A "wisdom-brief/1.0" \
  -H "Authorization: X-API-KEY $(grep '^WISDOM_CONNECT_API_KEY' ~/.env | cut -d= -f2-)"

Either way you should see one account, with your own name on it. Mine looks like this:

{ "accounts": [ { "id": "1001", "displayName": "WISdom Demo #1" } ] }

If you got an error instead, jump to Troubleshooting near the end. The two you are most likely to hit are both header problems, and neither means your key is bad.

The API, in one minute

WISdom Connect is a REST API at api.fortifiedwisdom.com. The full OpenAPI spec is published, which is what lets Claude write a correct client without you explaining anything to it:

https://api.fortifiedwisdom.com/docs/v1/openapi.json

Authentication is an API key, and the header format is exact. The header name is Authorization, and the value begins with X-API-KEY, one space, then your secret. No quotes, no extra spaces.

Authorization: X-API-KEY your_api_secret_here

Three endpoints do the work in this build:

  • GET /v1/accounts returns the accounts your key can see
  • POST /v1/accounts/{accountId}/top-statements:query returns ranked statements for a time window, and TOTAL_COST_USD is a first-class sort key
  • GET /v1/accounts/{accountId}/environment-events returns the change events logged against your estate

One behavior to know before you write a client: the gateway in front of the API rejects Python’s default “Python-urllib” User-Agent with a 403, which reads like an auth failure and isn’t. Set a real User-Agent on every request.

The query that does the heavy lifting

The cost data comes from top-statements:query. One POST body, two calls with different windows:

{
  "startTimestamp": "2026-09-01T00:00:00Z",
  "endTimestamp": "2026-09-08T00:00:00Z",
  "pagination": { "page": 1, "pageSize": 50 },
  "groupBy": "STATEMENT",
  "sortBy": "TOTAL_COST_USD",
  "sortDirection": "DESCENDING"
}

Each row that comes back carries instanceName, databaseName, objectName, sqlStatementHash, the statement text, and the totals: totalCostUsd, totalCpuMs, totalDurationMs, totalIoOperations, totalExecutionCount. The field that makes the diff trivial is sqlStatementHash. It is a stable identity for a statement across windows, so week-over-week comparison is a join, not a fuzzy match.

You could write the diff yourself in 80 lines of Python. Or you could not.

Hand it to Claude

Open Claude Code in an empty folder and paste the prompt below, or paste it into whichever assistant you are using: nothing in it assumes Claude. Paste it whole, exactly as it is. It is long on purpose: it front-loads every sharp edge we hit building this, which is why it runs clean in about one pass. You do not need to understand every line to use it, and the field notes after the demo explain what each constraint is defending against.

Read the OpenAPI spec at https://api.fortifiedwisdom.com/docs/v1/openapi.json.
My WISdom Connect API key is in a .env file in my home directory as
WISDOM_CONNECT_API_KEY. Have the script read it at runtime: don't read it
yourself, don't print it, and don't put it in the conversation. The auth header
is exact: "Authorization: X-API-KEY <key>". Set an explicit User-Agent on every
request: the gateway in front of this API returns 403 for Python's default
"Python-urllib/x.y" UA, which looks like an auth failure but isn't.
Write and run a Python script (stdlib only) that:
1. Gets my account from GET /v1/accounts. If the key can see more than one
   account, stop and list them rather than guessing. Accept a WISDOM_ACCOUNT
   variable (id or name fragment) to choose. Print which source the key came
   from and a short hash fingerprint of it, never the key itself.
2. Calls POST /v1/accounts/{id}/top-statements:query twice: top 50 by
   TOTAL_COST_USD, sortDirection DESCENDING, groupBy STATEMENT, pageSize 50,
   once for the last 7 days and once for the 7 days before that. Keep
   pagination.totalPages so the brief can say how large the full statement
   population is.
3. Joins the two windows on sqlStatementHash and classifies each statement:
   NEW (this week only), RISER (cost up >=25% and >=$10), DROPPER (down >=25%),
   FLAT. Statements present last week but absent this week are their own
   category, NOT droppers: the top-50 cutoff censors them, so report them as
   unconfirmed and print this week's #50 cost so the reader can judge.
4. For every riser, splits the change into volume vs cost per execution using
   totalExecutionCount:
       volume   = (n_now - n_prev) * (cost_prev / n_prev)
       per_exec = (cost_now/n_now - cost_prev/n_prev) * n_now
   The two must sum exactly to the cost delta; assert that. Where either
   window has zero executions, say attribution is unavailable rather than
   emitting a number.
5. Works around the fact that groupBy STATEMENT returns EMPTY instanceName,
   databaseName and objectName. So:
   - label statements with a descriptor derived from the statement text
     (e.g. "UPDATE dbo.OrderStatus") plus a short hash, and
   - build the per-database view from a second pair of calls using
     groupBy DATABASE, which does populate databaseName.
6. Gets GET /v1/accounts/{id}/environment-events for the last 14 days and flags
   any riser whose change window overlaps an event, matching on instance scope
   where the event names resources. Treat overlap as timing, not causation, and
   say so in the brief. Handle an empty event list cleanly; some accounts have
   none.
7. Writes a brief as brief.md and as a single-file brief.html:
   - Lead with a waterfall bridging last week's top-50 total to this week's:
     one bar per statement, biggest movers first, remainder bucketed.
     Per-statement deltas sum exactly to the difference between the two totals,
     so the bridge must close with no plug figure.
   - Then the five biggest risers with the per-execution math, new entrants,
     droppers worth confirming, the event correlations, and a grouped bar chart
     of week-over-week cost by database.
   - State the caveats in the brief itself: top-50 totals are not account
     totals, and how big the full statement population actually is.
   - brief.html must be self-contained with its own <meta charset="utf-8">,
     make no external requests, read correctly in light and dark, and keep wide
     tables scrolling inside their own containers so the page never scrolls
     sideways.
   - Both charts need nice-number y-axis ticks: no duplicate labels, no more
     than ~20% dead headroom above the data.
Before the live run, exercise the whole pipeline offline against synthetic
responses covering every branch (risers, droppers, new entrants, statements
that exited the top 50, a zero-execution row, and both scoped and account-wide
events), then verify the rendered HTML in a browser.
Use only read endpoints. Never print the key, and don't include raw statement
text for anything that isn't in the brief.

Claude reads the spec, dry-runs the whole pipeline against synthetic data first, then runs it live and hands you the brief. Open brief.html in your browser when it finishes. If something fails along the way, tell Claude what you saw and let it fix its own script. That loop is the point of working this way.

Every number and statement below is from a real run of exactly this pipeline against WISdom Demo #1, the demo account my key is scoped to:

Cost regression brief: WISdom Demo #1, Sep 10-17 vs Sep 3-10 (UTC)

Top-50 statement cost: $457.94 vs $414.21, up $43.73 (+11%). The top 50 is the head of roughly 21,600 ranked statements, not the account total. This week’s #50 costs $3.36, so anything below that line is invisible to this diff.

Almost none of the increase came from existing queries getting worse. Seven statements that were not in the top 50 last week arrived carrying $66.26 between them. That is the finding a ranking alone cannot give you.

Biggest mover, and it is one of those new entrants: SELECT ... as ErrorMessage from DT_Invoice ... AND (select Count(*) ...), $34.07 in its first week across 891,908 executions. Each individual call costs $0.000038. Nothing is wrong with any one of them; there are simply a great many, and the statement carries a correlated subquery in a per-invoice path. Cheap to fix now, expensive to ignore.

The opposite shape, from the same list: a SELECT * INTO #WORKTEMP_QS_ETL02 ETL step at $9.04 across two executions, $4.52 each. Same dollar ranking, two completely different conversations.

Biggest riser with a prior week to compare: an encrypted query hash (0x383B), $7.58 vs $21.15 (+179%). Executions went from 12.5 million to 36.3 million while the price per call went slightly down, so the split reads +$14.45 volume against -$0.88 price. The workload ran more, nothing regressed.

Droppers: two statements gave back $31.90. The sf_doc_permission_main retry update fell $16.06 on 122,000 fewer executions. The tpg.CACHE_COMP_DATA_LOAD_DETAILS version-counter write fell $15.84 while running 5% more often, which makes it price-led: the call itself got cheaper. That second one is the line you forward.

Exited the top 50: seven statements worth $33.87 last week don’t appear this week. Unconfirmed improvements, not wins; they may simply sit below the $3.36 cutoff.

No environment events were logged in either window, so nothing here has a change to correlate against. The brief says so instead of implying a cause.

A live run against WISdom Demo #1: the waterfall bridges last week’s top-50 total to this week’s with no plug figure, and the movers table shows whether each change came from volume or from the price of the call. Demo data throughout.

This is the common shape, and it is the argument for running the brief weekly rather than reaching for it after someone complains. The total moved because new work appeared, not because known queries degraded. A ranking alone would still have shown you that $34.07 statement sitting near the top of the list; only the diff tells you it was not there at all seven days ago. The droppers line is the part teams underrate. A regression brief is also a receipt: when tuning work lands, it shows the dollars returned, week after week.

Field notes from building this

The prompt above front-loads everything we learned the hard way, which is why it runs clean. What it routes around, for anyone extending it:

  • The 403 that isn’t auth. The gateway in front of the API rejects default stdlib User-Agents. If your header is correct and you still get 403, fix the UA before you touch the key.
  • Grain determines dimensions. groupBy: STATEMENT returns statement-level rows without instanceName, databaseName, or objectName populated, as of this writing. Label statements from their own text plus the hash, and make a second pair of calls at groupBy: DATABASE for anything per-database.
  • Pin the account. If your key can see more than one account, make the script stop and list them instead of taking the first. A brief against the wrong account is worse than no brief.
  • Top-N diffs censor. A statement that leaves the top 50 didn’t necessarily get cheaper; it may have dropped below the cutoff. Report exits as their own category and print the #50 cost so readers can judge.
  • Dry-run offline first. Exercising the pipeline against synthetic responses before the first live call caught four bugs here. The one-line assert that the volume/price split sums to the delta is the kind of invariant that keeps a brief honest.

Troubleshooting

  • 401 Unauthorized. The header format is off, not the key. The value must read X-API-KEY, one space, then the secret, with no quotes and no double space. A stray quote mark copied out of a document is the usual culprit.
  • 403 Forbidden with a header you know is right. This is the User-Agent behavior above, not permissions. Set any real User-Agent string and it clears.
  • npm or python is not recognized. The install did not add the tool to your PATH. Close the terminal, open a new one, and try again. If it still fails on Windows, rerun the Python installer and tick “Add python.exe to PATH.”
  • The script runs but the brief is thin or empty. The account probably has less than two weeks of collection history, so there is no prior window to diff against. Wait for a second week of data.
  • More than one account comes back. The script is built to stop and list them rather than guess. Set WISDOM_ACCOUNT to the id, or to a fragment of the name you want.
  • Anything else. Paste the error into the same Claude Code session that wrote the script. It has the full context and usually fixes it in one turn.

Schedule it

The brief is only useful if it shows up without being asked for. Two easy paths:

  • Claude Code headless. Save the prompt to a file and run claude -p "$(cat brief-prompt.txt)" on a timer, then pipe brief.md to your mail or Slack webhook of choice. On Windows, use Task Scheduler with a weekly Monday trigger pointed at a one-line PowerShell script; on macOS or Linux, use cron.
  • Claude API. If you’d rather own the loop, have Claude turn its own script into a scheduled job that calls the Claude API with the fresh data and posts the summary. Ask it; that’s a one-prompt refactor.

Either way, the key stays in the environment of the machine running the job.

Where to take it next

The brief is a sample, not the destination. Every team has its own version of the Monday question: the one somebody answers by hand because no dashboard asks it. Index churn by application, cost per release, the ad hoc workload nobody owns, a maintenance-window audit. Whatever yours is, the pattern holds: WISdom Connect supplies the priced data, and Claude, or whichever AI harness your team already runs, supplies the plumbing and the prose. Some starting points:

  • Log your deploys automatically. The environment-events endpoint also accepts POST, so your CI pipeline can log every release as an event. The correlation pass gets sharper with every deploy you record.
  • Feed the opportunities queue in. GET /v1/accounts/{id}/opportunity-events returns WISdom’s optimization findings with status and priority. Add a section to the brief: open opportunities older than 30 days, grouped by resource.
  • Wrap it as MCP tools. If your team uses Claude day to day, wrap top-statements, opportunity-events, and environment-events as Model Context Protocol tools and the brief becomes a conversation: “compare this week to last, ReportingDB only.”

Inside WISdom, Workload Compare answers the before/after question for a planned change like a migration, and “What Happened?” replays an incident. The regression brief covers the third case: the ordinary week where nothing paged, but something quietly got 54% more expensive.

Why this works at all

Most monitoring tools can rank statements by CPU or reads. The diff only turns into a Monday-morning sentence your CFO would accept because the ranking here is in dollars. WISdom is SQL Server performance monitoring with cost optimization built in, and the dollar cost of a query is not a report we render once: it’s data, in an API, that you can build on.

The other half of why it works is that you did not have to be a developer to do it. The first person outside our engineering team to run this build was a marketer, not a DBA. He got it working, and when our written steps lost him he did the obvious thing: he asked Claude how to set it up, and Claude told him. That is the shape of the whole exercise. The hard part is having the priced data in the first place.

Start a 30-day free trial, no credit card, at fortified.com.

The WISdom Connect docs live at docs.fortifiedwisdom.com/user-guide/wisdom-connect, and the OpenAPI spec is public. Bring Claude.

Share This Article

Twitter
LinkedIn

Related Articles

Downloads

The largest blind spot in FinOps is the data estate.

Today we are publishing FinOps for Data, a Fortified research paper on one of the largest blind spots in FinOps...

Blog Posts

SQL Server Activity Monitor: A Practical Guide for 2026

It is 2am, a report is timing out, and the first thing most DBAs do is right-click the instance in...

Blog Posts

Top SolarWinds Alternatives for SQL Server Monitoring in 2026

From Ben DeBow, founder of Fortified. A quick note on where this comes from, because it is not the usual competitor...

Get More Database Intelligence Insights

Subscribe to receive expert insights on SQL Server optimization, FinOps strategies, and performance intelligence.