Skip to content

Workflow · Data

Build a SQL / Data Analyst Copilot

A copilot that turns plain-English questions into validated SQL, runs read-only queries, and returns charts plus plain-language answers.

Intermediate4–6 hoursStack: Claude or GPT-5, Postgres / BigQuery / Snowflake, Schema metadata store, Streamlit or internal web UI
SQLAnalyticsDataCopilot
Edited by The AIKnowHub team · Editorial team

Problem

Data teams drown in 'quick question' Slack requests. Analysts repeat the same joins. Business users wait days for SQL they could've scoped in a sentence. Self-serve BI helps but non-analysts still bounce off metric definitions and table names.

Final output

An internal web app: analyst types a question, sees generated SQL (editable), runs against read-only replica, gets results table + auto-selected chart + narrative summary. Every query logged with user, SQL, and row count.

Architecture

User question (natural language)
  → Retrieve schema context (relevant tables/columns + metric definitions)
  → Retrieve similar past queries (few-shot)
  → SQL generation LLM → SELECT-only SQL
  → Validation layer (sqlglot parse, table allowlist, row limit injection)
  → Execute on read-only replica / warehouse role
  → Result profiler (row count, column types, sample stats)
  → Insight LLM → 2–3 sentence answer + chart spec
  → Render table + chart in UI

Step-by-step

  1. 01

    Schema catalog

    Export table/column descriptions, PK/FK relationships, and approved metric definitions (YAML). Refresh nightly from information_schema + your data catalog.

  2. 02

    Few-shot query library

    Collect 20–50 canonical queries analysts trust — revenue by month, churn cohort, activation funnel. Embed and retrieve top 3 by question similarity.

  3. 03

    SQL generation

    LLM with schema slice + few-shots. Require SELECT-only. Prefer CTEs over nested subqueries. Output: sql, explanation, tables_used, confidence.

  4. 04

    Validation gate

    Parse with sqlglot. Reject: INSERT/UPDATE/DELETE, multi-statement, tables outside allowlist. Inject LIMIT 1000 if missing. Block SELECT * on wide tables.

  5. 05

    Read-only execution

    Dedicated DB user with SELECT on approved schemas only. 30s query timeout. Log query hash to skip duplicate runs.

  6. 06

    Insight + visualization

    Profiler summarizes result shape. LLM picks chart type (bar/line/scatter/table) and writes 2-sentence insight. Flag low row counts and NULL heavy columns.

What you'll build

An internal copilot where analysts ask data questions in plain English, review generated SQL, run it safely against a read-only warehouse, and get answers with charts — without filing a ticket.

The safety model

Question → Generate SQL → Validate → Execute (read-only) → Explain
                ↑              ↑
           schema RAG      sqlglot + allowlist

Non-negotiable: the database role can SELECT and nothing else. Validation runs before execution, not after.

Step 1 — Schema catalog

Export metadata your analysts already trust:

tables:
  - name: analytics.fct_orders
    description: One row per completed order. Partitioned by order_date.
    columns:
      - name: order_id
        type: STRING
        description: Primary key
      - name: mrr_usd
        type: FLOAT
        description: Monthly recurring revenue attributed to this order

Pair with metric definitions:

metrics:
  - name: net_revenue
    sql: "SUM(mrr_usd) - SUM(refund_usd)"
    tables: [analytics.fct_orders]

Refresh nightly. Stale schemas produce confident wrong SQL.

Step 2 — Few-shot retrieval

Embed your library of approved queries. When a user asks "monthly churn by plan," retrieve:

  • churn_cohort_monthly.sql
  • subscription_cancellations_by_tier.sql

Few-shots teach join paths the schema alone doesn't convey. See Embeddings Explained and RAG Explained.

Step 3 — Generate SQL

Retrieve only relevant tables — embedding search over table descriptions, not the full warehouse.

The model outputs:

  • sql — the query
  • explanation — what it does
  • assumptions — "assuming churn = cancelled within 30 days"
  • confidence — flag low-confidence for human review

Show SQL in the UI before running. Let analysts edit inline.

Step 4 — Validation gate

Never skip this layer:

CheckAction
Parse SQLsqlglot — reject unparseable
Statement typeSELECT only
Table allowlistReject unknown tables
Row limitInject LIMIT 1000 if absent
TimeoutKill at 30 seconds
Cost estimateOptional — BigQuery bytes scanned

Rejected queries return a clear error — "table raw_events not in allowlist" — not a warehouse exception.

Step 5 — Execute read-only

-- DB role: analyst_copilot_ro
GRANT SELECT ON SCHEMA analytics TO analyst_copilot_ro;
-- No INSERT, UPDATE, DELETE, CREATE, DROP. Ever.

Run against a replica when possible. Log: user_id, question, sql, row_count, duration_ms, thumbs.

Step 6 — Insight + chart

Profile the result:

  • Row count, null rates, numeric ranges
  • Sample 10 rows for the insight model

The model returns a headline ("Enterprise churn spiked to 4.2% in May"), chart spec, and caveats.

Render with Streamlit, Plotly, or your internal chart library.

Context engineering matters

The quality ceiling is retrieval, not model size:

  • Table descriptions beat raw information_schema dumps.
  • Metric definitions prevent "revenue" meaning three different things.
  • Approved query few-shots teach join paths.

See Context Engineering Explained for the broader pattern.

MCP alternative

If your team lives in Claude Desktop or Cursor, wrap this as MCP tools:

  • list_tables — searchable schema
  • generate_sql — question → validated SQL
  • run_query — read-only execution

One implementation, multiple clients. Start with the MCP server from your API workflow and point tools at your warehouse API layer.

Rollout plan

  1. Week 1 — 3 analysts, 10 approved tables, manual schema.
  2. Week 2 — Add validation metrics, thumbs up/down.
  3. Week 4 — Expand allowlist if run-without-edit > 60%.
  4. Week 8 — Open to PMs with narrower table scope.

Extensions

  • Query explain — "why is this slow?" with EXPLAIN output interpretation.
  • Semantic layer — dbt metrics as the source of truth instead of raw tables.
  • Scheduled questions — "email me MRR every Monday" from saved queries.
  • Anomaly mode — compare this week's result to trailing 4-week average automatically.

The copilot doesn't replace analysts — it absorbs the repetitive 60% so they focus on the questions that need judgment.

Prompt examples

Copy any of these, replace the placeholders, run.

SQL generation system prompt

You are a {{warehouse}} SQL analyst for {{company}}.

Rules:
- SELECT statements only. No DDL, DML, or multiple statements.
- Use fully qualified table names: {{schema}}.table_name
- Prefer explicit column lists — never SELECT * on tables with >20 columns.
- Join using documented FK relationships only.
- Add WHERE filters for partition keys when tables are date-partitioned.
- If the question is ambiguous, state assumptions in "assumptions" field.
- If impossible with available schema, say so — do not invent tables.

Schema (relevant tables):
{{schema_context}}

Similar approved queries:
{{few_shot_queries}}

Output JSON:
{
  "sql": string,
  "explanation": "one paragraph",
  "tables_used": string[],
  "assumptions": string[],
  "confidence": 0-1
}

Question: {{user_question}}

Result insight

Summarize these query results for a business stakeholder.

Question: {{user_question}}
SQL: {{sql}}
Row count: {{row_count}}
Column summary: {{column_stats}}
Sample rows (max 10): {{sample_rows}}

Output JSON:
{
  "headline": "one sentence answer",
  "insight": "2-3 sentences with specific numbers",
  "caveats": string[],
  "chart": { "type": "bar"|"line"|"scatter"|"table", "x": string, "y": string }
}

Rules:
- Quote exact numbers from the sample — do not extrapolate beyond returned rows.
- If row_count < 5, warn about small sample.
- If the question isn't answered by these columns, say what's missing.

Cost estimate

Line itemApprox. cost
SQL generation (Sonnet, per question)$0.06
Insight + chart spec (Haiku)$0.01
Warehouse compute (typical ad-hoc)$0.02
Per 1,000 questions$90.00
Streamlit / internal hostingexisting infra
Total per run (approx.)~$90.09

Costs depend on model choice, content length, and how aggressively you cache.

Optimizations

  • Retrieve only 5–8 relevant tables via embedding search — not the full 200-table schema.
  • Cache results by SQL hash for 1 hour — analysts re-run the same question often.
  • Pre-approve 'golden queries' as templates analysts pick instead of free-form gen.
  • Use a cheaper model for SQL gen on simple aggregations; escalate for multi-join questions.
  • Nightly sync schema metadata — stale column names are the

Common mistakes

  • Write access on the DB role — one hallucinated DELETE is a career event. Read-only only.
  • Stuffing the entire schema into context — confuses the model and burns tokens.
  • No query timeout — a bad join bricks your warehouse slot.
  • Skipping the validation layer — LLMs emit plausible but wrong SQL constantly.
  • Letting users skip SQL review — show SQL by default until trust is established.

Frequently asked questions

All work. BigQuery and Snowflake have information_schema for metadata export. Postgres is simplest for v1 on a read replica. The validation + generation pattern is identical.