Text-to-SQL is now table stakes in every warehouse UI, but the assistant does not know your grain, your soft-deletes, or that orders double-counts because of the 2024 backfill. This playbook is the discipline that makes generated SQL trustworthy: schema-in-context prompting, a verification query pair, and an EXPLAIN-driven optimization loop.
Tier: Foundation · Workflow area: Analytical SQL
Skip it for one-table lookups you can type faster than you can prompt.
EXPLAIN.SHOW CREATE TABLE / information_schema), plus 3–5 sample rows per table.Build a context block once per schema area. Paste DDL, grain, and known traps:
Dialect: BigQuery standard SQL.
Tables:
- fct_orders (grain: one row per order ITEM, not order): order_id, item_id,
customer_id, order_ts (UTC), status, gross_amount, is_deleted
- dim_customers (grain: one row per customer): customer_id, country,
acquired_channel, created_at
Traps: is_deleted=true rows must be excluded; order_ts is UTC, business
reports use Asia/Kolkata; gross_amount is in paise (divide by 100).
Save this block; reuse it in every session. Quality of generated SQL tracks quality of this block almost linearly.
Ask for the query plus its assumptions:
Question: monthly revenue and distinct purchasing customers by country for
2025, top 10 countries by total revenue.
Write the SQL, then list every assumption you made (deduplication, filters,
timezone, currency, ties) as bullets I can veto.
Read the assumptions before the SQL — vetoing a wrong assumption is faster than debugging its consequences.
Run the verification pair before trusting results. For any aggregate query, generate two cheap checks:
SELECT SUM(gross_amount)/100 FROM fct_orders WHERE ...) — must match to the rupee.SELECT order_id, item_id, COUNT(*) ... GROUP BY 1,2 HAVING COUNT(*) > 1 on any table you assumed unique. Ask the assistant to write both; run them yourself.Eyeball edge rows. Pull 5 rows feeding one output cell (one country-month) and hand-verify inclusion, timezone bucketing, and the paise conversion.
Optimize with EXPLAIN in the loop. Paste the plan, not vibes:
Here is the EXPLAIN output and the query. It scans 1.4TB. Propose rewrites
in order of expected impact (partition pruning on order_ts, pre-aggregation,
join reordering, approximate distinct where acceptable). For each, state the
trade-off and whether results can change.
Re-run EXPLAIN after each accepted rewrite; require "results unchanged" rewrites to be verified with the step-3 reconciliation.
Comment and commit. Have the assistant add a header comment documenting question, grain, filters, and verification date. Uncommented generated SQL becomes unmaintainable folklore within a month.
A query that runs and returns plausible numbers is the most dangerous failure mode in analytics — verification queries, not eyeballing the output, are what catch it.
ref('fct_orders')) instead of re-deriving filters that dbt models already apply — double-applied filters are a classic bug here.APPROX_COUNT_DISTINCT).orders.revenue when the column is gross_amount. Real DDL in context prevents most of this; a dry-run/compile catches the rest.DATE_TRUNC vs TRUNC vs date_trunc('month', ...) — always state the dialect and validate with a dry run before a full scan.COUNT(DISTINCT ...) for an approximate version without flagging it to consumers.fct_orders → t1, mapping kept locally), or use the warehouse vendor's in-platform copilot, which operates under your existing data-processing agreement.