Skip to content
Dev Ideas
Go back

Reliable Natural Language Database Querying with LLMs-1

Edit page

“Ask your database” is quickly becoming a non-negotiable feature in modern BI systems. From small teams to large enterprises, analysts expect to query data in natural language, without needing to understand SQL or underlying schemas, and get answers instantly.
Reliable Natural Language Database Querying with LLMs The promise is simple. Making it reliable in production is not.

Table of contents

Open Table of contents

Modelling the problem and Analyzing Failure Modes

Assume we are dealing with the following data model and a user asks: ER Diagram

Natural Language Query:

Show me the top 3 product categories by revenue for customers in the US region, who signed up after 2025, excluding orders with less than 2 units, for the last quarter

The solution can be modelled if we:

  1. Capture the user’s natural language query
  2. Inject database schema context (tables, fields, relationships, sometimes sample data)
  3. Prompt the LLM to translate the query into SQL
  4. Execute the generated SQL against the database

Looks like a simple task at first, until we start investigating the failure modes:

Answering the wrong question

for the discussed query, the following ambiguities may arise:

RequirementHidden ambiguityPossible mistake
RevenueWhich metric? Gross, net, after discounts?Uses the wrong revenue column
Product categoriesWhich category hierarchy?Groups at the wrong level
US regionBilling, shipping, or customer location?Filters the wrong customers
Signed up after 2025Exact date and timezone?Applies the wrong date filter
Orders with ≥2 unitsOrder-level or item-level quantity?Filters incorrectly
Last quarterCalendar or fiscal quarter?Uses the wrong date range
Top 3Ranked by revenue, orders, or something else?Returns the wrong results

Performance Issues

So for the discussed query, the following ambiguities may arise :

Natural Language Query:

Show me the top 3 product categories by revenue for customers in the US region, who signed up after 2025, excluding orders with less than 2 units, for the last quarter

Illustrative example only

This SQL was generated using GPT-5 for demonstration purposes. LLMs are non-deterministic, and different models—or even different runs of the same model—may produce entirely different SQL.

-- Example query generated by LLM
SELECT
    p.category,
    SUM(oi.quantity * oi.unit_price) AS revenue
FROM orders o
JOIN order_items oi
    ON o.id = oi.order_id
JOIN products p
    ON oi.product_id = p.id
JOIN customers c
    ON o.customer_id = c.id
WHERE
    YEAR(o.order_date) = 2026
    AND QUARTER(o.order_date) = 2
    AND c.region = 'US'
    AND c.signup_date > '2025-01-01'
GROUP BY p.category
HAVING SUM(oi.quantity) >= 2
ORDER BY revenue DESC
LIMIT 3;
IssueWhat’s wrong?Better approach
Non-sargable predicateYEAR(order_date) and QUARTER(order_date) prevent index usage.Filter using a date range (order_date >= ... AND order_date <ResponsiveTable ...).
Late aggregation filterHAVING SUM(quantity) >= 2 filters after grouping every category. If the requirement is to exclude small orders, the filter should happen before aggregation.Filter qualifying orders before computing revenue.
Incorrect aggregation semanticsSUM(quantity) >= 2 checks the total quantity per category, not whether each order contains at least 2 units.Aggregate at the order level first, then roll up to categories.
Inefficient join orderAll order_items are joined before restricting to the relevant customers and time period.Filter orders and customers first to reduce rows flowing into the joins.
Missing partition pruningApplying functions to order_date may prevent partition elimination on partitioned tables.Use range predicates on the partition key.
What does SARG mean?

SARG stands for Search ARGument. A predicate is sargable when the database engine can efficiently use an index to satisfy the condition. Operations such as YEAR(order_date), LOWER(email), DATE(created_at), or arithmetic expressions on indexed columns typically make predicates non-sargable, forcing the optimizer to scan significantly more data than necessary.

-- Non-sargable
WHERE YEAR(order_date) = 2025
-- Sargable
WHERE order_date >= '2025-01-01'
  AND order_date < '2026-01-01'

Security Risks

Generating SQL directly from natural language introduces a new attack surface. Unlike traditional applications where queries are carefully authored and reviewed, LLM-generated SQL must defend against ambiguous requests, malicious prompts, and accidental misuse.

Illustrative example only

This SQL was generated using GPT-5 for demonstration purposes. LLMs are non-deterministic, and different models—or even different runs of the same model—may produce entirely different SQL.

-- User: "Show me all customer information for users in the US."

SELECT
    c.*,
    cc.card_number,
    cc.cvv,
    ssn.ssn,
    a.api_key
FROM customers c
LEFT JOIN credit_cards cc
    ON c.id = cc.customer_id
LEFT JOIN customer_ssn ssn
    ON c.id = ssn.customer_id
LEFT JOIN api_keys a
    ON c.id = a.customer_id
WHERE c.country = 'US';
IssueWhat’s wrong?Better approach
Unauthorized data accessUses SELECT * and joins sensitive tables containing payment information, SSNs, and API keys that were never requested.Restrict access using role-based permissions and only expose approved columns.
Data exfiltrationReturns significantly more data than the user asked for, including sensitive information.Return only the fields required to answer the user’s question.
Prompt injectionA malicious prompt could instruct the model to ignore previous restrictions and query sensitive tables.Separate authorization from prompting and validate every generated query before execution.
Destructive queriesA model could generate UPDATE, DELETE, DROP, or ALTER statements if prompted.Execute generated SQL using read-only credentials or allowlisted query types.
Denial of serviceSimilar queries using large cross joins or unbounded scans can consume excessive CPU, memory, and I/O.Apply query timeouts, row limits, resource quotas, and cost-based validation before execution.

Edit page
Share this post:

Next Post
Reflection in Golang