All posts
AI & ML

Text-to-SQL: Letting LLMs Query Your Database Safely

Let anyone ask your database a question in plain English and get an answer — that is the promise of text-to-SQL, and LLMs finally make it work. But you are letting a model write code that runs on your data, so accuracy and safety are everything. Here is how to do both.

Dhileep Kumar6 min read
Text-to-SQL: Letting LLMs Query Your Database Safely

Every company sits on a database that only a handful of people can actually query, while everyone else files requests and waits. Text-to-SQL dissolves that bottleneck: a user asks a question in plain English, an LLM writes the SQL, and the answer comes back in seconds. It is one of the most immediately useful things you can build on top of a model.

It is also one of the easiest to build dangerously. You are taking untrusted natural language, turning it into executable code, and running that code against your production data. Get accuracy wrong and people trust bad numbers; get safety wrong and a clever or careless question deletes a table. Doing text-to-SQL well means taking both seriously.

The two hard problems

Text-to-SQL has exactly two challenges, and they pull in different directions: making the query correct, and making it safe to run. You cannot trade one for the other — a correct query that drops data is a disaster, and a safe query that returns the wrong number is just a quieter one.

  • Accuracy: the model must understand your schema. It needs table and column names, types, relationships, and often sample rows to write correct joins and filters.
  • Ambiguity: real questions are vague. Active users this month means different things in different schemas, and the model has to resolve it the way you intend.
  • Safety: the model writes code that runs on your data. Nothing should be able to write, drop, or read across tenants, no matter what the question is.
  • Performance: a naive query can scan a billion rows. Generated SQL needs limits and timeouts so one question cannot take down the database.

Accuracy comes from context

The model cannot query a schema it cannot see. The biggest lever on accuracy is feeding it the relevant tables, columns, types, and relationships — plus a few example questions paired with their correct SQL, and sometimes sample rows so it understands the data, not just the names. A curated, well-described schema beats a bigger model every time.

From there, two more moves help a lot: let the model retry when a query errors, feeding the database's error message back so it can self-correct; and put a semantic layer or set of vetted metrics in front, so common questions map to known-good SQL instead of being re-derived each time.

Safety is non-negotiable

Treat every generated query as hostile until proven harmless. The single most important defense is to run it on a read-only, least-privilege connection that physically cannot write, drop, or reach data the user is not allowed to see — so even a perfectly malicious query simply fails:

python
# Text-to-SQL: give the model the schema, generate a query, then GUARD it before running.
def ask(question, schema, model, db):
    sql = model.complete(f"Schema: {schema} Question: {question} Return ONE read-only SELECT.")
    if not is_safe(sql):              # reject writes, DDL, multiple statements, suspicious calls
        raise ValueError("unsafe query")
    return db.run_readonly(sql)       # execute on a least-privilege, read-only connection

# Defense in depth: a read-only role, a statement allowlist, row limits, and a hard timeout.

Never rely on prompting the model to be careful — that is advice, not enforcement. The real protection lives in the database role and the validation layer, which hold no matter what text the user supplies or how the model is jailbroken.

In text-to-SQL, the model is the injection. The natural-language question becomes code that runs on your data — so the database, not the prompt, has to be where safety is enforced.

Shipping it responsibly

  • Feed the schema and examples. Accuracy tracks how well you describe your tables, columns, relationships, and a few question-to-SQL examples.
  • Run read-only, least-privilege. A connection that cannot write or cross tenant boundaries makes whole classes of disaster impossible.
  • Validate before executing. Parse the SQL and reject writes, DDL, multiple statements, and anything outside a SELECT allowlist.
  • Cap cost. Enforce row limits and query timeouts so a broad or pathological query cannot exhaust the database.
  • Show the SQL and let it self-correct. Display the generated query for transparency, and feed execution errors back so the model can fix and retry.

The bottom line

Text-to-SQL turns a database into something everyone can ask questions of — but only if you nail accuracy and safety together. Give the model a well-described schema and examples for correctness, and run every generated query through validation on a read-only, least-privilege connection with limits and timeouts for safety. The model proposes the query; your guardrails decide whether it ever runs.

Think of it as letting a smart stranger write queries against your data: helpful, fast, and absolutely not to be trusted with write access. Build it that way and text-to-SQL is a superpower; build it naively and it is an incident waiting to happen.

Share

Enjoyed this?

Get the next deep dive in your inbox. No spam — just the stories worth reading.

Subscribe to the newsletter

Comments