Skip to work
All work
No. 292026Execution-based eval · Real Bedrock run

Text-to-SQLExecution Accuracy, Not String Match

Natural-language questions to SQL over a real SQLite database, scored by execution accuracy across four prompting strategies, with the failure modes tagged so each strategy is credited for what it actually fixes.

Headline results

Execution accuracy
83.3% → 100% few-shot
Self-correction
No-op on the real model
Exact match
25%, under-counts by 58 points
Scoring
Run both queries, compare rows

System architecture

System architecture diagram for text-to-sql
Fig. 1 — Text-to-SQL — system architectureFull size

Problem

Text-to-SQL benchmarks routinely grade by string match against a gold query, and that metric is broken in both directions. A correct query written with a different alias, column order, or whitespace mismatches the gold yet returns identical rows, while a syntactically valid query can silently return the wrong rows when a join condition goes missing. Only running both queries and comparing result sets tells you whether the SQL is actually right.

Approach

Every prediction executes against a seeded in-memory SQLite e-commerce database (customers, products, orders) and its rows are compared to the gold query's rows as an order-insensitive multiset, order-sensitive only when the gold query has an ORDER BY. Four prompting strategies run over the same 12 questions: zero-shot builds the prompt from the schema, few-shot prepends worked examples, self-correction re-prompts with the SQLite error message on an execution failure, and the fourth combines both. Questions are tagged by the failure mode they trigger, semantic (valid SQL, wrong rows) or syntax (SQL that throws), so the benchmark reports accuracy within each subset rather than one blended number. The real path runs Claude Haiku 4.5 on AWS Bedrock.

Impact

On the real model self-correction was a no-op. Claude Haiku made essentially no syntax errors, syntax-subset accuracy sat at 1.00 even zero-shot, so self-correction scored 83.3%, identical to zero-shot. The residual failures were all semantic, the semantic subset sat at 0.75, and few-shot examples alone repaired those to 1.00 and lifted the whole set to 100%. String match under-counts hard: zero-shot scored 83.3% by execution but 25.0% by exact match, a 58-point gap. The calibrated mock assumed both fixes were needed and the live run overturned it.

Decisions & tradeoffs

Score by executing both queries

Exact match is still computed, but only to contrast. Running the prediction and the gold against the same database and comparing rows is the only check that catches a valid query returning wrong rows while crediting a correct query that happens to be worded differently.

Tag the failure mode per question

An aggregate accuracy cannot say why a strategy helped. Splitting questions into semantic traps and syntax traps is what exposed that the real model's syntax subset was already at 1.00, which is the whole reason self-correction had nothing to catch.

Keep the mock as a contrasting fixture

The offline mock has designed orthogonal failure modes and reproduces a different table, zero_shot 41.7% rising to 100.0% only with both fixes. Keeping it next to the real numbers makes the gap between the assumed story and the measured one explicit, and lets CI reproduce every mock number with no key.

Build spec

Questions
12 over seeded SQLite
Best strategy
few_shot, 100.0% exec
Zero-shot
83.3% exec / 25.0% exact
Model
Claude Haiku 4.5 on Bedrock
Stack
SQLite, Bedrock, pytest

System notes

  • Few-shot alone hit 100% exec accuracy, self-correction was a no-op at 83.3%, tied with zero-shot
  • String match under-counts by 58 points: 83.3% exec vs 25.0% exact on zero-shot
  • Result sets compared as an order-insensitive multiset, order-sensitive only under ORDER BY
  • Questions tagged semantic vs syntax so subset accuracy shows which strategy fixes which mode

What this does not show

  • The real model makes almost no syntax errors, so the self-correction stage that helps the mock does nothing here. The mock is kept as a contrasting fixture.

Stack

Python · SQLite · Claude · AWS Bedrock · Evaluation · pytest

View source on GitHub
Next project
MCP Analytics Server · Governed MCP Gateway Between an LLM and a Database