Plain-English questions against the warehouse, answered in seconds instead of a two-week data-team queue — that's real, and so is the failure mode: a syntactically perfect query that answers a subtly different question. This playbook adds the discipline that makes self-serve safe: schema context in, query inspection always, and a known-good cross-check before any number travels.
Workflow area: Analytics & Experimentation · Tier: Foundation · Hands-on time: ~30 minutes
The dangerous thing about NL-to-SQL is that it almost never fails loudly. The query runs, the chart renders, the number is plausible — and the join double-counted users with multiple workspaces, or "active" meant something different from your dashboard's "active." A wrong number delivered confidently is worse than no number, because it ships upward in a slide. Every step here exists to catch the quiet failure.
Assemble schema context once. Table names, column meanings, known traps — the tribal knowledge every data team carries: "use events_prod not events_raw; user_id is null for anon sessions; timestamps are UTC; org-level metrics must dedupe on org_id." Store this as a reusable block (or rely on Snowflake Cortex Analyst / BigQuery's Gemini features, which read schema metadata directly — and still benefit from the traps list).
Ask with the question pinned down. In Claude, or your warehouse's native NL interface:
Write a SQL query ([DIALECT: Snowflake/BigQuery/Postgres]) to
answer this question.
Question: [PLAIN ENGLISH — e.g. "How many distinct organizations
had at least one bulk approval in July 2026?"]
Definitions: [PIN THE AMBIGUITY — e.g. "organization = org_id in
orgs table, excluding internal/test orgs (is_internal = true);
July = July 1-31 UTC"]
Schema context:
[PASTE SCHEMA BLOCK]
Rules: before the SQL, restate the question as you understood it,
in one sentence. Comment every JOIN and WHERE with why it's there.
State any assumption you made where my definitions were silent.
Inspect before you run. Good output looks like:
Understood question: count of distinct non-internal orgs with ≥1
expense_bulk_approved event, 2026-07-01 to 2026-07-31 UTC.
SELECT COUNT(DISTINCT e.org_id)
FROM events_prod e
JOIN orgs o ON o.org_id = e.org_id -- org attributes
WHERE e.event_name = 'expense_bulk_approved'
AND o.is_internal = FALSE -- excl. test orgs
AND e.event_ts >= '2026-07-01'
AND e.event_ts < '2026-08-01'; -- July UTC, exclusive end
Read three things every time: the restated question (is it your question?), the date boundaries (inclusive/exclusive, timezone), and every JOIN (does it fan out rows before a COUNT?). Assumptions the model stated are decisions you now make explicitly.
Run the known-good cross-check — the step that earns trust. Before quoting the result anywhere, reconcile it against a number you already trust. Ask the model to write a second query reproducing a certified dashboard figure (say, "total active orgs in July") using the same tables and filters. If the reproduction matches the dashboard, your filters and joins agree with the certified definition and your novel number inherits that trust. If it doesn't match, stop — nothing from this session travels until you know why.
Sanity-check the magnitude yourself. Is the answer physically plausible — more bulk-approving orgs than total orgs? A number 40x last month's? Cheap arithmetic against known totals catches the fan-out joins the eye missed.
Log what you learned. Every trap you catch ("events_prod has dupes on retry — dedupe on event_id") goes into the schema-context block. Your context file compounds; within a month it's the best onboarding doc the data team never wrote.
Query results can be PII even when tables are locked down — a CSV of emails pasted into a consumer chatbot is a data breach. Use in-warehouse AI or your approved tenant, aggregate before you extract, and never paste row-level customer data into external tools.
NL-to-SQL removes the queue, not the need for verification. Inspect the query, reconcile against a known-good number, sanity-check the magnitude — twenty minutes of discipline that turns "the AI said 4,200" into a number you'd defend in the exec review.