DEV Community

Arisyn
Arisyn

Posted on

How I Would Benchmark a Text-to-SQL System for Production

A clean question against a clean schema is a demo. A production benchmark should deliberately test ambiguity, missing relationships, competing metrics, and SQL that executes successfully but answers the wrong business question.

Most Text-to-SQL evaluations start with questions like:

What was revenue last quarter?
Show sales by region.
List the top 10 customers.
Enter fullscreen mode Exit fullscreen mode

These tests are useful for checking whether the basic pipeline works.

They are not enough to tell you whether the system is ready for enterprise production.

In production, the difficult cases are rarely caused by SQL syntax alone. They come from business language, incomplete metadata, ambiguous metrics, undocumented relationships, aggregation grain, and assumptions that look reasonable but are wrong.

So if I were benchmarking a Text-to-SQL system, I would build the benchmark around failure paths, not just happy paths.


## Why Execution Accuracy Is Not Enough

A typical evaluation might measure:

Question
   ↓
Generated SQL
   ↓
Expected SQL
Enter fullscreen mode Exit fullscreen mode

or:

Generated SQL
   ↓
Execute
   ↓
Compare Result
Enter fullscreen mode Exit fullscreen mode

Execution-based evaluation is better than exact SQL matching because multiple SQL statements can produce the same correct answer.

But enterprise reliability requires additional questions:

Did the system understand the business term correctly?

Did it choose the authoritative metric?

Did it use the right relationship path?

Did it preserve the correct aggregation grain?

Did it recognize ambiguity?

Did it know when not to query?
Enter fullscreen mode Exit fullscreen mode

A query can execute successfully and still fail every one of these tests.


# Build a Failure-Path Benchmark

I would create at least seven test categories.

A. Ambiguous Intent
B. Business Term Mapping
C. Competing Metrics
D. Missing Relationships
E. Executable-but-Wrong SQL
F. Insufficient Intent
G. Environment Change
Enter fullscreen mode Exit fullscreen mode

Each category tests a different part of the system.


## Test A: Ambiguous Intent

Question:

Show me our best customers.

Possible interpretations:

Revenue
Profit
Growth
Retention
Lifetime Value
Enter fullscreen mode Exit fullscreen mode

The benchmark should not define best in the question.

The correct behavior depends on enterprise context.

If a governed definition exists:

term: best_customer
metric: customer_lifetime_value
status: active
Enter fullscreen mode Exit fullscreen mode

the system can resolve it.

If multiple valid definitions remain, the expected behavior should be:

CLARIFY
Enter fullscreen mode Exit fullscreen mode

not:

GENERATE_SQL
Enter fullscreen mode Exit fullscreen mode

### Example benchmark case

{
  "id": "ambiguity_001",
  "question": "Show me our best customers.",
  "valid_metrics": [
    "revenue",
    "profit",
    "growth",
    "retention"
  ],
  "governed_default": null,
  "expected_action": "clarify"
}
Enter fullscreen mode Exit fullscreen mode

### What to score

Ambiguity detected?
Correct candidates identified?
Clarification triggered?
Clarification options useful?
Enter fullscreen mode Exit fullscreen mode

This gives you a Clarification Accuracy metric rather than only SQL accuracy.


## Test B: Business Term Mapping

Question:

What was revenue by product code this quarter?

Now make sure the schema does not contain a field called:

product_code
Enter fullscreen mode Exit fullscreen mode

Instead:

product_master.material_id
sales_detail.sku_no
inventory.item_code
product_dim.prod_master_id
Enter fullscreen mode Exit fullscreen mode

The benchmark should contain a governed semantic mapping:

business_term:
  name: Product Code

maps_to:
  table: product_master
  field: material_id
Enter fullscreen mode Exit fullscreen mode

### Failure mode

A schema-similarity system may select:

inventory.item_code
Enter fullscreen mode Exit fullscreen mode

because the name looks closest.

But the benchmark expects:

product_master.material_id
Enter fullscreen mode Exit fullscreen mode

because that is the governed enterprise definition.

### What to score

Business term resolved correctly?
Correct physical field selected?
Governed mapping preferred over lexical similarity?
Enter fullscreen mode Exit fullscreen mode

Call this:

# **Semantic Mapping Accuracy**


## Test C: Competing Metrics

Create several plausible financial fields:

sales_order.total_amount
invoice.invoice_amount
finance_revenue.recognized_amount
payment.received_amount
Enter fullscreen mode Exit fullscreen mode

Question:

What was revenue last quarter?

All four fields are relevant to money.

Only one represents the governed Revenue metric.

Benchmark definition:

metric:
  name: Revenue

source:
  table: finance_revenue
  field: recognized_amount

time_field:
  finance_revenue.recognition_date
Enter fullscreen mode Exit fullscreen mode

### What to inspect

Do not only inspect the final number.

Inspect the query plan.

Expected:

Metric
→ Revenue

Field
→ finance_revenue.recognized_amount

Time
→ finance_revenue.recognition_date
Enter fullscreen mode Exit fullscreen mode

### What to score

Metric resolution accuracy
Source selection accuracy
Time-field accuracy
Enter fullscreen mode Exit fullscreen mode

This catches systems that accidentally return the right number from the wrong business definition.


## Test D: Missing Relationships

Production databases often lack perfect foreign keys.

Build:

customer
account
sales_order
finance_revenue
Enter fullscreen mode Exit fullscreen mode

Expected relationship path:

Customer
   ↓
Account
   ↓
Order
   ↓
Revenue
Enter fullscreen mode Exit fullscreen mode

Then deliberately remove some database constraints.

The benchmark should still contain enough data evidence for a relationship system to infer or retrieve valid relationships.

For example:

account.customer_id
sales_order.account_id
finance_revenue.order_id
Enter fullscreen mode Exit fullscreen mode

### Useful relationship evidence

A relationship engine might consider:

Column names
Data types
Uniqueness
Value overlap
Inclusion
Known metadata
Validated relationships
Enter fullscreen mode Exit fullscreen mode

One useful inclusion signal is:

Inclusion(A → B)
=
|distinct(A) ∩ distinct(B)|
---------------------------
|distinct(A)|
Enter fullscreen mode Exit fullscreen mode

where A is a candidate foreign-key-like column and B is a candidate referenced column.

### What to score

Correct tables selected?
Correct relationship path found?
Unsupported direct joins avoided?
Enter fullscreen mode Exit fullscreen mode

Call this:

# **Relationship Path Accuracy**


## Test E: Executable-but-Wrong SQL

This is the test I would care about most.

Suppose the schema allows:

Customer → Order
Enter fullscreen mode Exit fullscreen mode

and:

Customer → Account → Order
Enter fullscreen mode Exit fullscreen mode

Both can generate executable SQL.

But for consolidated customers, only:

Customer → Account → Order
Enter fullscreen mode Exit fullscreen mode

preserves the correct business grain.

Create two candidate queries.

### Query A

SELECT
    c.customer_name,
    SUM(o.amount)
FROM customer c
JOIN sales_order o
    ON c.customer_id = o.customer_id
GROUP BY c.customer_name;
Enter fullscreen mode Exit fullscreen mode

### Query B

SELECT
    c.customer_name,
    SUM(o.amount)
FROM customer c
JOIN account a
    ON c.customer_id = a.customer_id
JOIN sales_order o
    ON a.account_id = o.account_id
GROUP BY c.customer_name;
Enter fullscreen mode Exit fullscreen mode

Both may execute.

Only Query B is business-valid for the benchmark scenario.

### What to score

SQL executable?
Business relationship valid?
Aggregation grain valid?
Result semantically correct?
Enter fullscreen mode Exit fullscreen mode

This creates an important distinction:

Execution Accuracy
≠
Business Accuracy
Enter fullscreen mode Exit fullscreen mode

## Test F: Insufficient Intent

Question:

Show our best-performing products recently.

The benchmark intentionally leaves unresolved:

best-performing
recently
Enter fullscreen mode Exit fullscreen mode

Possible metric candidates:

Revenue
Profit
Units Sold
Growth
Enter fullscreen mode Exit fullscreen mode

Possible time candidates:

7 Days
30 Days
Current Month
Current Quarter
Enter fullscreen mode Exit fullscreen mode

If no governed defaults exist, the expected result is not SQL.

It is:

{
  "action": "clarify",
  "unresolved": [
    "metric",
    "time_range"
  ]
}
Enter fullscreen mode Exit fullscreen mode

### What to score

Did the system detect missing intent?
Did it avoid premature SQL generation?
Did it ask the minimum useful clarification?
Enter fullscreen mode Exit fullscreen mode

This measures:

# **Safe Failure Accuracy**

A production system should not be rewarded for answering every question.


## Test G: Environment Change

Static benchmarks miss one of the largest production costs: maintenance.

Start with a working environment.

Then introduce changes:

Add a table
Rename a field
Add a new metric
Deprecate an old metric
Add a new relationship
Change a semantic mapping
Enter fullscreen mode Exit fullscreen mode

For example:

Before:
Product Code → product_master.material_id

After:
Product Code → product_dim.product_code
Enter fullscreen mode Exit fullscreen mode

Then rerun the benchmark.

Measure:

How many cases break?

How much manual configuration is required?

How quickly does the system recover?

Which knowledge objects need updating?
Enter fullscreen mode Exit fullscreen mode

This produces a metric that benchmark leaderboards rarely show:

# **Semantic Maintenance Cost**


# Score More Than SQL

A useful benchmark report could look like:

Metric Score
Semantic Mapping Accuracy 94%
Metric Resolution Accuracy 96%
Relationship Path Accuracy 91%
Clarification Accuracy 93%
SQL Execution Accuracy 98%
Business Answer Accuracy 89%
Safe Failure Accuracy 95%
Maintenance Effort 3.2 min/change

The exact metrics depend on the product.

The important point is that:

SQL Accuracy
Enter fullscreen mode Exit fullscreen mode

should be one row, not the entire benchmark.


# Use a Multi-Stage Evaluation Harness

Instead of treating Text-to-SQL as one black box:

Question
→ SQL
Enter fullscreen mode Exit fullscreen mode

capture intermediate artifacts.

For example:

{
  "question": "Revenue by customer last quarter",

  "intent": {
    "metric": "recognized_revenue",
    "dimension": "customer",
    "time": "last_quarter"
  },

  "semantic_objects": [
    "metric.revenue",
    "dimension.customer"
  ],

  "tables": [
    "customer",
    "account",
    "sales_order",
    "finance_revenue"
  ],

  "relationship_path": [
    "customer.customer_id -> account.customer_id",
    "account.account_id -> sales_order.account_id",
    "sales_order.order_id -> finance_revenue.order_id"
  ],

  "generated_sql": "...",

  "action": "execute"
}
Enter fullscreen mode Exit fullscreen mode

Now failures become diagnosable.

If the final answer is wrong, you can determine whether the failure came from:

Intent
Semantic Mapping
Table Selection
Relationship Resolution
Metric Selection
SQL Generation
Execution
Enter fullscreen mode Exit fullscreen mode

That is much more useful than:

FAILED
Enter fullscreen mode Exit fullscreen mode

# Build Adversarial Test Pairs

A strong benchmark should include pairs of questions that look similar but require different interpretations.

Example:

Q1

What was our revenue last quarter?

Expected metric:

recognized_revenue
Enter fullscreen mode Exit fullscreen mode

Q2

What was our invoiced amount last quarter?

Expected metric:

invoice_amount
Enter fullscreen mode Exit fullscreen mode

Another pair:

Q1

Sales by customer region.

Expected dimension:

customer_region
Enter fullscreen mode Exit fullscreen mode

Q2

Sales by billing region.

Expected dimension:

billing_region
Enter fullscreen mode Exit fullscreen mode

These pairs test whether the system is actually resolving semantics or simply reusing the most common mapping.


# Include Negative Tests

Traditional benchmarks mostly contain questions that should be answerable.

Production benchmarks should include questions that should not.

For example:

Question:
"Show profitability by happiness score."

No governed metric:
happiness_score

Expected:
Cannot resolve metric / clarification required
Enter fullscreen mode Exit fullscreen mode

Or:

Question:
"Join employee salary with public customer data."

Expected:
Rejected by policy
Enter fullscreen mode Exit fullscreen mode

The exact safety rules depend on the deployment.

The principle is:

A system should be tested on its ability to refuse invalid query plans, not only generate valid ones.


# Test With Messy Schemas

Do not benchmark only:

customer
orders
products
Enter fullscreen mode Exit fullscreen mode

Use schemas that look more like enterprise reality:

t_cust_m
cust_master_old
acct_rel
f_ord_h
ord_detail_v2
fin_rev_rec
inv_hdr
inv_line
Enter fullscreen mode Exit fullscreen mode

Add:

Deprecated tables
Duplicate concepts
Missing descriptions
Inconsistent naming
Cross-system IDs
Partial foreign keys
Enter fullscreen mode Exit fullscreen mode

If the product claims to work with enterprise data, the benchmark should look like enterprise data.


# Measure Human Setup Cost

Before the first benchmark run, track setup effort.

For example:

Schema descriptions:       4 hours
Metric definitions:        3 hours
Relationship configuration: 6 hours
Example SQL:                5 hours
Prompt tuning:              2 hours
Enter fullscreen mode Exit fullscreen mode

Total:

20 hours
Enter fullscreen mode Exit fullscreen mode

Another system may achieve slightly lower raw accuracy but require:

4 hours
Enter fullscreen mode Exit fullscreen mode

of setup.

That difference matters.

So benchmark:

Accuracy
Enter fullscreen mode Exit fullscreen mode

and:

Cost to achieve that accuracy
Enter fullscreen mode Exit fullscreen mode

together.


# A More Useful Production Score

I would think about production value approximately as:

                  Accuracy × Trust × Coverage
Value  ≈  ─────────────────────────────────────
                Setup + Maintenance Cost
Enter fullscreen mode Exit fullscreen mode

Again, this is not a universal mathematical formula.

It forces the benchmark to consider what enterprises actually pay for.

A system that scores 97% on 50 carefully prepared questions may be less valuable than one scoring 93% across thousands of messy tables with far less manual configuration.


# What I Would Put in a POC

If I were designing a vendor POC, I would include:

10 clear questions
10 ambiguous questions
10 semantic-alias questions
10 competing-metric questions
10 missing-relationship questions
10 multi-path join questions
10 executable-but-wrong traps
10 questions that require clarification
10 unanswerable questions
10 post-schema-change regressions
Enter fullscreen mode Exit fullscreen mode

That gives:

100 cases
Enter fullscreen mode Exit fullscreen mode

with intentionally different failure modes.

Then evaluate both:

Can it answer correctly?
Enter fullscreen mode Exit fullscreen mode

and:

Can it recognize when it should not answer yet?
Enter fullscreen mode Exit fullscreen mode

# Final Thoughts

A production Text-to-SQL benchmark should not primarily test whether an LLM knows SQL.

Modern models are already good at SQL generation under clean conditions.

The harder questions are:

Did it understand the business term?

Did it select the right metric?

Did it find the right data?

Did it use the right relationship?

Did it preserve the right grain?

Did it detect ambiguity?

Did it fail safely?

How much human work was required?
Enter fullscreen mode Exit fullscreen mode

Those are system-level questions.

So when evaluating a Text-to-SQL platform:

Don't benchmark the easiest route from question to SQL.

Deliberately break the assumptions.

Remove the foreign key.

Add three plausible revenue fields.

Use business terminology that does not match the schema.

Create two executable join paths where only one is correct.

Ask a question that should trigger clarification.

Then change the data model and run everything again.

That is where you start learning whether you have a good demo—or a production system.

Top comments (0)