SQL-from-schema recipe for Qwen
Feed Qwen a table schema and get correct, dialect-aware SQL with a self-check pass.
#data #coding
What vetted this — trust report
Goal
Turn a table schema plus a plain-English request into correct, dialect-aware SQL — with a self-check pass, so column names and joins actually line up instead of merely looking right.
Generated SQL fails in a specific and dangerous way: it runs, returns rows, and the rows are wrong. Nothing in your tooling catches that. This recipe is built around forcing the failure to be visible.
Recipe
1. Give it the ground truth
Paste the exact CREATE TABLE statements — or the ORM models, or \d+ output.
Not a description of the schema; the schema. Include:
- Column types and nullability.
NULLsemantics drive half the wrong answers. - Primary and foreign keys, and unique constraints — they determine join
multiplicity, which determines whether your
SUMis right. - Indexes, if performance matters.
- Enum values and their meaning, if a status column drives the filter.
State the dialect: Postgres, MySQL, SQLite, SQL Server, BigQuery, DuckDB.
2. State the request precisely
The three things people leave out, in order of how often they cause a wrong answer:
- Grain. "One row per customer per month" — say it explicitly. Ambiguous grain is the number-one cause of silently wrong aggregate queries.
- NULL handling. Do customers with no orders appear with zero, or not appear?
That's the difference between a
LEFT JOINand anINNER JOIN, and the model cannot guess your intent. - Boundaries. Are date ranges inclusive at both ends? What timezone are the timestamps in, and what timezone should the grouping use? Ties in a top-N — dense rank, or arbitrary?
3. Ask for the query plus a rationale
Write the SQL, then explain in 2–3 lines which tables you joined on which keys and why. Use only columns that appear in the schema above. If the request is ambiguous, state the interpretation you chose rather than picking silently.
That last clause is what surfaces the ambiguity you didn't notice you had.
4. Self-check pass
A separate turn, not a clause in the first one:
Verify every table and column you referenced exists in the schema above. List any that don't. Check that
GROUP BYcovers every non-aggregated column inSELECT. Check each join for multiplicity: could any join produce more rows than its left side, and if so does that inflate an aggregate? Check whether filters belong inWHEREorHAVING.
Run it as its own turn because verification against the generation in the same breath tends to rubber-stamp.
5. Dry-run on three rows
Here are 3 sample rows per table. Walk through the query and show the exact result set. Confirm the row count and grain match what I asked for.
This catches fan-out, wrong-direction joins, and off-by-one date boundaries faster than reading the SQL does — and much faster than running it on real data and eyeballing plausible-looking numbers.
6. Run it read-only, on a bounded slice
First execution should be against a replica or with a LIMIT, and never inside a
transaction that writes. Compare the row count against your expectation from
step 5 before you trust any number in the output.
Common traps to prompt against
- Invented columns.
created_datewhen your schema sayscreated_at. The most common single failure, and the self-check pass is what catches it. - Join fan-out inflating
SUM/COUNT. Joining orders to order_items and summingorders.totalmultiplies the total by the item count. Join to a pre-aggregated subquery, or aggregate before joining. COUNT(*)vsCOUNT(col)vsCOUNT(DISTINCT col). Three different questions; models pick the first one.NOT INwith NULLs returns nothing at all. UseNOT EXISTS.- Filtering an outer join in
WHEREsilently turns it into an inner join. The condition belongs in theONclause. - Dialect drift:
LIMITvsTOPvsFETCH FIRST;||vsCONCAT;date_truncvsDATE_FORMATvsstrftime;truevs1;ILIKEvsLOWER(...) LIKE. - Timezones.
date_trunc('day', ts)on atimestamptzgroups by UTC days, which is not what a report for a New York team means by "day".
Output
Final SQL in one block, the join rationale, the assumptions it had to make, and the expected grain. Keep the assumptions — they're what you check when the number looks wrong three weeks later.
Where this recipe does not help
A schema you don't understand. If you can't tell whether the returned number is plausible, no prompting technique saves you — you're just automating the production of confident wrong answers. Learn the two or three tables first.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.