SQLore
FeaturesPricingDownloadAboutBlogContact
Start freeStart for free
SQLore
HomeFeaturesSolutionsPricingDownloadCompareFAQAboutBlogContact

SQLore © 2026

TermsPrivacyRefunds
Download for macOSDownload for WindowsSign in
  1. Home
  2. Solutions
  3. The technique
The technique

Natural Language To SQL, Done Against A Real Schema

The gap between a query that parses and a query that is right is your schema. Here is how that gap gets closed.

Download SQLoreSee all features

What is natural language to SQL?

Natural language to SQL, also called text-to-SQL, is the translation of a plain-English question into an executable SQL query against a specific database. It requires more than a language model: the system has to resolve the question against the real schema — the tables, keys and column values that exist — which is the step known as schema linking, and the step that decides whether the query is correct.

Schema linking is the whole problem

Translating "how many customers churned last month" into SQL is trivial once you know that churn is `subscriptions.cancelled_at`, that a customer is a row in `accounts` rather than `users`, and that the two join through `account_id`. Knowing that is schema linking, and it is where systems fail. The SQL grammar is small and every current model has it; your schema is large, private, and nothing was trained on it.

Why you cannot just paste the schema into the prompt

It works for the sample database in the tutorial. At two thousand tables the schema does not fit in a context window, and even where it fits, burying eight relevant tables in two thousand irrelevant ones measurably degrades the answer. SQLore builds a retrieval index over the schema locally — names, types, keys, distinct values, statistics — and searches it per question, so the model sees the eight tables the question is about and not the rest.

  • Introspect once, cache, refresh when migrations land.
  • Retrieve per question, on your machine, with no schema upload.
  • Include the foreign keys, so the join path is available rather than inferred.
  • Include distinct values for low-cardinality columns, so a filter matches what is stored.

Execution is feedback, and feedback is what makes it agentic

A one-shot system hands you a query and you find out at the meeting that the join fanned out. An agentic system runs the query itself and reads what came back. Zero rows from a question that should return thousands is a signal. A sum that is exactly four times too large is a signal. SQLore acts on those signals before returning an answer, which is why the query you are shown is the one that survived rather than the first one drafted.

The safety property that makes execution acceptable

Letting a model run generated SQL against your database is only reasonable if the generated SQL provably cannot do damage. SQLore parses every statement to an AST and validates it down to a single read-only SELECT — no writes, no DDL, no multiple statements, no stacked queries — then runs it on a read-only connection with a statement timeout. The guarantee is structural, so the agentic loop costs you nothing.

A worked example

You ask: “Revenue by category last quarter, excluding refunded orders”
select c.name as category,
       sum(oi.quantity * oi.unit_price_cents) / 100.0 as revenue
  from order_items oi
  join orders     o on o.id = oi.order_id
  join products   p on p.id = oi.product_id
  join categories c on c.id = p.category_id
 where o.placed_on >= date_trunc('quarter', current_date) - interval '3 months'
   and o.placed_on <  date_trunc('quarter', current_date)
   and o.status <> 'refunded'
 group by c.name
 order by revenue desc;

Not one word of that question maps to a column. There is no revenue column — revenue is quantity multiplied by unit_price_cents, held in cents, which is why the query divides by a hundred. There is no category on an order either; the path runs from order_items through products to categories, and it was read out of the foreign keys rather than inferred from the names. And "refunded" is spelled the way it is actually stored in orders.status, because introspection cached the distinct values of that column. A model given this question and no schema misses all four, and still returns a query that runs.

How it works, step by step

  1. 1

    Introspect the schema

    On first connection it reads the system catalog: tables, columns, types, primary and foreign keys, and statistics. The result is cached locally so this happens once, not per question.

  2. 2

    Retrieve the relevant slice

    Your question is matched against that index on your own machine. A two-thousand-table database yields the eight tables the question is about.

  3. 3

    Generate the query

    The model writes SQL against the retrieved slice — real table names, real keys, real column values — on a local model by default or a live one if you asked for it.

  4. 4

    Validate and execute

    The statement is parsed to an AST, validated to a single read-only SELECT, and executed on a read-only connection under a statement timeout.

  5. 5

    Read the result and correct

    If the result contradicts the question, it revises and runs again. What you are shown is the answer in plain English with the result charted or tabulated, and under it the query that worked, the rows it returned and how long it took.

Frequently asked questions

How accurate is natural language to SQL?

Accuracy depends almost entirely on schema grounding rather than on the model. A system with no access to your schema will produce plausible, wrong queries against any non-trivial database; one that retrieves the right tables and keys and then checks its own result by executing it is in a different category. Because the query is always shown alongside the answer, you are never asked to take accuracy on trust.

Does it handle joins across many tables?

Yes — that is what the foreign keys in the retrieval index are for. The join path between two tables is looked up rather than guessed, which is the specific failure that makes multi-table questions unreliable in tools that only see column names.

What happens with ambiguous questions?

Ambiguity that the schema can resolve is resolved from the schema. Ambiguity that it cannot — two plausible date columns, two tables that both look like "orders" — comes back as a question rather than a guess, and you can encode the answer as a rule so it is not asked twice.

More answers on the main FAQ, or ask us directly.

Related

For people who already write SQL

An AI SQL Assistant That Shows Its Working

Read more

The category, explained

The AI Database Assistant That Runs On Your Machine

Read more

For analysis

An AI Database Query Tool For The Questions Between Dashboards

Read more

Every page in this section covers a different question about the same product. See all solutions.

SQLore platform

Stop Writing The
Same Three Joins

Start free, upgrade when you are asking enough questions for it to matter.

Start for freeContact sales
sqlore.com

New chat

Search chats

TODAY

Orders by month

PREVIOUS 7 DAYS

Top products by revenue

PREVIOUS 30 DAYS

Repeat customer rate

Revenue by category

Average order value

Refunds by reason

Customers by city

Cart abandonment

Slowest shipping lanes

Discount impact on margin

Inventory turnover

Order statistics

SQLoredesktopretail_db · default⌘K

Order statistics for this year

Reasoning — 2 steps, 2 queries

Total orders · Delivered · In transit · Cancelled

1 row · 10 ms

Stat

15,600

Total orders

13,942

Delivered

1,412

In transit

246

Cancelled

orders by category

4 rows · 11 ms

LineDonutBar
8,4006,3004,2002,1000
ACCESSORIESPERIPHERALSFURNITUREDISPLAYS

There are 15,600 orders in retail_db for the year to date.

  • Status: 13,942 have been delivered and 1,412 are still in transit.
  • Category: most sit in Accessories (8,420), followed by Peripherals (4,180), Furniture (2,140) and Displays (860).

Revenue by payment method

Reasoning — 1 step, 1 query

Ask about your database…

Answers come from SQL run against your database.