The schema is the context
Every wrong SQL answer traces back to the same gap: the model guessed your columns. Paste the tables and their columns inline: 'users(id, email, created_at, plan)' is enough. This one line prevents more errors than any amount of instruction about writing good SQL.
Name the dialect and the version
Postgres 15, MySQL 8 and SQLite disagree about window functions, date arithmetic and upserts. A query that is correct in one is a syntax error in another. One clause, 'You are writing PostgreSQL 15', removes a whole class of failure.
Say what a row means
Ask for the exact columns and their units: 'email, order count, and total in rupees to two decimals'. Money in particular goes wrong quietly. If the column stores paise and you wanted rupees, an unlabelled number is a bug you will find in production.
Say how big the tables are
A query that is correct on ten thousand rows can be unusable on ten million. The model cannot see your row counts, so it optimises for readability and hands you a correlated subquery that reads well and times out in production. One clause fixes it: 'orders has about 40 million rows, users about 200,000'. Scale is what decides whether a join, a window function or a lateral is the sensible shape. Add which columns are indexed if you know them, because a filter on an unindexed column is the difference between a query that returns and one that does not. An order of magnitude is enough if you do not know exactly. The model is choosing between strategies that differ by factors of a thousand, not by percentages.
Keep it read-only unless you mean it
Ask for a SELECT, and say so rather than assuming it. A model asked to 'clean up duplicate users' will write you a DELETE, and it will look correct. Two rules are worth carrying in every SQL prompt you run against real data: 'return a SELECT only, never UPDATE or DELETE', and 'if the task needs a write, show me the SELECT that finds the affected rows first'. The second is the useful half. It turns a destructive operation into a reviewable list, which is what you wanted before running anything against production anyway. None of this is specific to AI. It is the discipline you would apply to a query a colleague sent you, and the model is a colleague who has never seen your data.
Make it declare what it assumed
Even with the schema pasted, the model is guessing at things the column names do not tell it. Whether email is unique. Whether deleted_at being null means active. Whether created_at is UTC or local. Whether a user with no orders should appear with a zero or not appear at all. Add: 'list every assumption you made about the data, before the query'. What comes back is usually three or four lines, and one of them is usually wrong in a way that would have produced a plausible but incorrect number. That is the whole value. A wrong query that errors is cheap to find. A wrong query that returns a number is the expensive kind, and assumptions are where those come from.