Text-to-SQL · Guardrails

Text-to-SQL with Guardrails: Querying Postgres in Plain English

Your analysts should not need to write JOIN clauses to answer a business question. That is the entire promise of Text-to-SQL: type "how many churned customers did we lose this quarter", and get the right query back. The danger is that the same technology, pointed at a production primary with no guardrails, can also DROP a table, scan 40 million rows, or invent a column that does not exist.

This article explains why "just ask the LLM" fails in production, and the four checks that make natural-language SQL safe enough to hand to a whole team.

The promise and the risk

Modern language models are genuinely good at translating natural language into SQL — for toy schemas. Real schemas break them in three predictable ways:

  • Hallucinated columns and tables. The model guesses a column name that looks plausible but does not exist, and the query fails at runtime.
  • Destructive statements. A careless prompt like "clean up the old orders" can become DELETE FROM orders instead of SELECT.
  • Catastrophic scans. A question that sounds cheap — "show me all orders" — can translate to a multi-million-row scan that saturates the database.

None of these are LLM bugs per se. They are the natural failure modes of a tool with no feedback loop, no context, and no one holding the door.

Why raw LLM SQL fails in production

In a demo, the model sees a three-table schema and a clean prompt. In production, it sees 40 tables, ambiguous column names, and questions built on assumptions about the data. The model has no way to verify its output — and the database, left to itself, will happily execute whatever it is told.

The fix is not a better model. It is a pipeline that checks every step, the same way a code review checks every commit.

Four stages, in this order

Instead of one black-box call, split the work into stages that can each be checked — and shown as they complete:

1
Resolve the question
An analyst writes the question the way they would ask a colleague. No SQL, no table names, no knowledge of the schema required.
2
Resolve the terms
Every business expression resolves through your glossary — what your company means by "active customer", not what a model assumes.
3
Generate and validate
The statement is built from the resolved terms, and the indexes it wants to rely on are checked against the real schema.
4
Show it before it runs
You read the SQL before your database does. Nothing is hidden behind a "generate" button and a spinner.

Each stage is reported to the panel as it completes, so a slow or failed step is visible rather than interminable.

The four checks that are not optional

Showing the SQL is table stakes. The checks that actually protect the database are these:

  • Your glossary is the authority. A term that is defined resolves to your definition — the tables, the expression, the aliases. A term that is not defined gets surfaced for someone to define, instead of being silently guessed at.
  • The schema is the referee. Columns and indexes are validated against the real database, so a plausible-looking column name that does not exist never makes it into the output.
  • You see the statement first. The generated SQL is printed before anything runs, which means the review happens in the panel and not in production.
  • Every generation is recorded. Each generated query is an event in the append-only, hash-chained audit log — so "who asked for this and when" has an answer that does not depend on anyone's memory.

What it looks like in practice

Here is the shape of a guarded session. The user asks:

"give me the 10 customers with the most revenue in the last quarter,
 excluding the ones marked as churned, and also show how many orders they placed."

The pipeline resolves "revenue" and "churned" against the glossary, confirms that customers.status and the join to orders exist, validates the index it wants to use against the live schema, and prints the SELECT for you to read. If the question had leaned on a term nobody has defined, you would get that term back flagged for definition — a to-do, not a fabrication.

The takeaway: Text-to-SQL stops being scary the moment the database stops trusting the model and starts trusting your own definitions instead.

Watch the SQL appear before it runs.

Praxion resolves plain-language questions against your glossary and validates them against the real schema.

See the playground →