Skip to content
Grounded ERP AI

Text-to-SQL accuracy: why benchmark scores don't survive your ERP

Text-to-SQL accuracy on academic benchmarks collapses on real ERP schemas. The six causes, and the five mechanisms that raise it in production.

ERPray teamUpdated 7 min read
Short answer

Text-to-SQL accuracy measured on academic benchmarks does not transfer to enterprise ERP schemas, because the hard part is not SQL syntax, it is deciding what the question means. A model scoring well on a 20-table benchmark faces 200 record types, hundreds of custom fields and business terms like “open” that have no fixed definition. Accuracy comes from grounding, validation and refusal, not model size.

Key takeaways

  • Benchmark text-to-SQL accuracy is measured against a reference query someone already wrote. In production nobody has decided what the question means, so there is nothing to be accurate against.
  • Execution accuracy is the more honest metric, but a query can execute, return rows and still be wrong — a bad status filter produces a believable number with no error.
  • The six real failure sources are schema size, custom fields, ambiguous business terms, unobserved status domains, multiple valid join paths, and an undefined question.
  • What raises accuracy is narrowing the guess: schema grounding, validation before execution, clarifying questions, the query shown for human checking, and refusal when unsure.
  • The only accuracy figure worth anything is measured on your own account, your custom fields and your definitions.

A text-to-SQL demo is easy to make look perfect. Point a model at a clean eight-table sample database, ask for last quarter's revenue by region, and it writes correct SQL first time. Point the same model at a live NetSuite account with 200 record types, 300 custom fields and four subsidiaries, and text-to-SQL accuracy drops sharply. Nothing about the model changed. The schema did, and so did the question.

This matters because the failure is quiet. Wrong SQL does not usually throw an error. It runs, returns a number, and the number looks reasonable. That is worse than a crash.

What text-to-SQL accuracy actually measures

Published accuracy figures almost always come from one of two measures. Neither is the thing you care about.

MeasureWhat it checksWhat it misses
Exact matchThe generated SQL is structurally equivalent to a reference queryPenalises correct queries written differently. A query that returns the right answer through a different join order can score zero.
Execution accuracyThe generated SQL returns the same result set as the reference query on a test databaseRewards queries that are right by accident on small test data, and says nothing about whether the reference query answered the real question.
Useful answerA controller would defend the number in a meetingNot measured by any benchmark, because it depends on your definitions, your period conventions and your custom fields.
Execution accuracy is the honest one of the two that get reported. It still isn't usefulness.

Execution accuracy has a blind spot that matters enormously in ERP: a query can execute, return rows, and be wrong. Filter on a status value that exists but means something else and you get a plausible total with no warning. We covered the underlying mechanism in why AI hallucinates on ERP data — the model is not lying, it is completing a pattern, and a pattern that reads well is not the same as a pattern that is true of your account.

Six reasons accuracy collapses on a real ERP schema

Schema size and naming entropy

Benchmark databases have between five and thirty tables with human-readable names. A production ERP has hundreds of record types, and the names are historical rather than logical. In NetSuite, transaction and transactionline hold sales orders, invoices, purchase orders, journals and item receipts in the same structures, separated by a type column. Nothing in the names tells you that. A model that has never seen your account has to infer the mapping from hundreds of candidates, and inference at that width is where errors enter.

Custom fields carry the business logic

The fields that matter most to your business are usually the ones nobody outside your company has ever seen: custentity_credit_tier, custbody_original_po, a custom record holding freight quotes. These are invisible to any model trained on public data. They are also frequently the correct answer. If credit tier lives in a custom field, a query about credit exposure that ignores it is not slightly wrong, it is answering a different question. This is the whole argument for a per-account ERP data dictionary instead of generic schema knowledge.

Business terms have no fixed definition

Ask ten people in one company what revenue means and you get at least three answers: invoiced, recognised, or collected. Open is worse. An open sales order might mean not yet fulfilled, not yet billed, or not yet closed, and those three sets overlap without matching. Customer can mean the billing entity, the parent, or the ship-to. None of this is a SQL problem. It is a definitions problem that SQL then encodes. If you have ever argued about whether DSO should exclude cash sales, you already know the shape of it.

Status domains have to be observed, not assumed

Statuses are the most common source of quietly wrong answers. Status values are shaped by your workflow and your customisations, and the label a user sees rarely equals the value stored on the record. A model asked for overdue invoices will cheerfully write a filter on a status called Overdue, because that is what the phrase suggests. No such status exists. Overdue is a computation over due date, amount remaining and transaction type. The only reliable route is to read the distinct values that actually exist in the account and filter against those.

-- guessed: a status label the schema has never contained
WHERE t.status = 'Overdue'

-- grounded: real ageing logic, with status values read from the account
WHERE t.type = 'CustInvc'
  AND t.status IN ( /* observed open-invoice values for this account */ )
  AND t.duedate < CURRENT_DATE
  AND t.foreignamountunpaid > 0
The difference between assuming a status domain and reading it.

Several join paths are valid, only one is right

Between a customer and an item there are several defensible routes: through sales orders, through invoices, through fulfilments, or through the item's own sales history. Each returns a different number, and each is correct for a different question. Benchmarks with a single foreign-key path never test this. Real schemas force a choice the model has no basis to make. Ambiguity here is not a model failure, it is a missing requirement.

The question itself has no ground truth

Every benchmark item ships with a reference query, which means a human already decided what the question meant. In production nobody has. “How much are we owed?” has no reference answer until someone rules on credit memos, unapplied cash, intercompany balances and disputed invoices. Any accuracy figure therefore measures agreement with one person's interpretation. Change the interpreter and the score changes, without a single line of SQL changing.

Free calculator
SuiteQL formatter

Paste the SQL an AI wrote for you, format it, and read what it actually filters on before you trust the number.

What raises accuracy in practice

None of the fixes are about a bigger model. They are about narrowing the space the model has to guess in, and checking its work before a number reaches a human.

  1. 1.Ground the query in the account's real schema. Retrieve the record types, fields, status domains and join paths relevant to this specific question, and put them in front of the model. That converts an open-ended generation problem into a constrained one. It is the difference between writing SQL from memory and writing it with the dictionary open. What grounded ERP AI means covers the mechanism in full.
  2. 2.Validate before you execute. Parse the generated query and check every table, column and join against the schema. A field that does not exist is then caught structurally rather than empirically. This removes the most embarrassing failure class entirely, because a hallucinated column cannot survive validation.
  3. 3.Execute defensively, then sanity-check the result. Run with row limits and a timeout. Look for the signatures of a wrong query: zero rows where rows are expected, a count larger than the base record population, a total that moved by an order of magnitude against last period. Cheap checks catch a surprising share of silent errors.
  4. 4.Ask instead of assuming. When a term is genuinely ambiguous, the correct output is a question, not a query. “Revenue — invoiced or recognised?” takes the user eight seconds to answer and removes an entire error class. A system that never asks is optimising for demo smoothness.
  5. 5.Show the query, every time. Verification cannot be delegated to the thing being verified. When the SQL sits under the answer, someone who knows the business can spot a wrong filter in seconds, even without reading SQL fluently. Shown work turns an opaque claim into a checkable one.
  6. 6.Refuse when the answer isn't derivable. If the data needed is not in the account, or the request maps to nothing in the dictionary, saying so is the correct output. A system that always answers has, by construction, no way to tell you when it should not have.

How to evaluate text-to-SQL on your own data

You can run a credible evaluation in an afternoon. It needs no framework, only discipline about what counts as correct.

  1. 1.Write down 30 questions your team actually asks, in the words they use. Include five you know are ambiguous.
  2. 2.Have the person who owns each number define the correct answer in writing, including the definitional choices. This is the expensive step, and the one that makes the exercise worth doing at all.
  3. 3.Score three things separately: did it produce a query, did the query execute, did the number match. The gaps between the three are the real finding.
  4. 4.Score the ambiguous five differently. The right result there is a clarifying question. A confident answer is a fail, however good the number looks.
  5. 5.Repeat monthly. Custom fields get added and statuses get renamed, so a system that is not re-grounded will drift away from your account.
Failure modeWhat it looks likeHow you catch it
Hallucinated fieldQuery references a column that does not existSchema validation before execution — it fails loudly, which is the good case
Wrong status filterQuery runs and returns a believable number that is too high or too lowReconcile once against the report finance already trusts
Wrong join pathRow counts inflated by fan-out; totals exceed every source reportCompare the row count against the base record count
Wrong definitionNumber is internally consistent but answers a different questionRead the shown query. Only a human who owns the metric catches this one
Silent empty resultZero reported as if it were a findingTreat zero rows as a prompt to re-check, never as an answer
Four of these five are catchable by machine. The fourth is why the query has to be visible.

Where draws the line

treats accuracy as a property of the whole system rather than the model. During onboarding it builds a data dictionary of your specific account: record types, standard and custom fields, subsidiaries, real join paths, and status domains read from your data instead of assumed. Queries are generated from that dictionary and validated against it before they run, which makes a hallucinated field structurally impossible. Every answer carries the exact SuiteQL that produced it. When a question is ambiguous it asks, and when the answer is not in the account it says so.

That is a narrower promise than a benchmark percentage, and deliberately so. If you want to see how much definitional choice hides inside a single number, the AR aging calculator makes it concrete: bucket boundaries, credit memos and the reserve rate each change the overdue figure without anyone writing bad SQL.

Frequently asked questions

What is text-to-SQL?

Text-to-SQL is the task of turning a question written in ordinary language into a database query, running it, and returning the result. It is how conversational analytics tools answer questions about live data. The generation step is well understood; the difficulty in enterprise use is knowing which tables, fields and status values the question actually refers to.

How accurate is text-to-SQL on real enterprise databases?

Lower than published benchmark figures, and the gap widens with schema size. Benchmarks use small, clean, well-named databases with a reference query per question. A production ERP has hundreds of record types, custom fields nobody outside the company has seen, and business terms with no agreed definition. Measure accuracy on your own schema; other people's numbers do not transfer.

What is execution accuracy in text-to-SQL?

Execution accuracy checks whether the generated query returns the same result set as a reference query when both are run against a test database. It is fairer than exact match, because it accepts queries written differently that produce the same answer. Its weakness is that a query can execute successfully and return a wrong but believable number.

Why does AI write the wrong SQL for my ERP?

Usually because it guessed at something it could not see: a custom field, a renamed status, or which of several valid join paths you meant. ERP schemas encode business decisions in names that make no sense from outside. Without the account's real schema and status values in front of it, the model fills the gap with something plausible.

Can text-to-SQL handle custom fields?

Only if the system reads your schema first. A model has no prior knowledge of a field like custentity_credit_tier, so it either ignores the field or invents one. Systems that build a per-account data dictionary and generate queries from it treat custom fields as first-class; systems relying on general training data cannot.

Should you let AI run SQL against a production ERP?

Reading, yes, with controls: a least-privilege read-only role, row limits, timeouts, and the query shown alongside every answer so a human can verify it. Writing is a different decision that needs preview, explicit confirmation, audit logging and undo. The risk is rarely a broken query — it is a confident wrong number reaching a decision.

Your ERP already knows. Start asking.

ERPray computes answers like these live from your own ERP account and shows the exact query behind every number. Early access is open for NetSuite teams — free plan at launch.