{"course":{"slug":"data-and-sql","title":"Data, SQL and Getting to the Answer","url":"https://addaly.com/learn/data-and-sql","level":"beginner","track":"build"},"lesson":{"slug":"sql-from-an-ai","title":"Asking an AI for SQL without being lied to","url":"https://addaly.com/learn/data-and-sql/sql-from-an-ai","position":12,"of":66,"minutes":8,"answer":"Paste your schema, ask for assumptions first, and check the number against something you already know.","keyIdea":"Paste your schema, ask for assumptions first, and check the number against something you already know."},"body":{"format":"markdown","text":"## What an AI is actually good at here\n\nBe fair to the tool before you are suspicious of it. A language model will write a window function you would have spent twenty minutes looking up. It will translate a query between Postgres and BigQuery dialects. It will explain a query plan you cannot read. It will produce a decent first draft five times faster than you can type one. This is real and you should use it.\n\nWhat it cannot do is know things it was not told, and its failure mode is not silence. It is confidence.\n\n## Give it the schema, every time\n\nThe most common wrong query from an AI is not a logic error. It is a guess about your column names, dressed as knowledge. It will write `orders.total_amount` when your column is `amount_cents`. It will invent a `users.last_login_at` because most schemas have one.\n\nSo paste the actual definitions. `\\d orders` in psql, or the CREATE TABLE statements, plus one sentence per table saying what a row means:\n\n> Here are three tables. One row of `payments` is one attempted charge, including failures. One row of `orders` is one basket someone checked out. An order can have several payment attempts.\n\nThat last sentence (the one about several payment attempts) prevents more wrong queries than anything else you can write, because it is exactly the fact that produces row multiplication in a join.\n\n## Ask for the assumptions before the SQL\n\nA useful prompt shape:\n\n> Before writing the query, list what you are assuming about the schema and about the definitions of the terms I used. Then write the query.\n\nThis converts invisible guesses into a list you can check in ten seconds. It will say things like \"I assumed 'active' means a login in the last 30 days\" and \"I assumed refunds are stored as negative amounts in the same table.\" One of those is usually wrong, and it is much cheaper to catch it there than in the output.\n\n## Verify like you would verify a stranger's work\n\nBecause that is what it is.\n\n**Run it with a LIMIT first and read the rows.** Not the count. The rows.\n\n**Check the row count against what you expect.** If the orders table has 40,000 rows and the joined result has 61,000, a join is multiplying, and the AI's total revenue is now wrong in a way that looks entirely plausible. This is the single most dangerous failure in AI-written SQL: the result is not absurd, it is just too big by 40%.\n\n**Check one row by hand.** Pick a customer, look them up in the source, verify their number.\n\n**Compare a total to something you already know.** Last month's figure, the finance export, anything.\n\n**Break it on purpose.** Change a filter so the number should move. If it does not move, the filter is not doing anything.\n\n> Figure: Getting SQL out of a model without being confidently misled. Steps: 1. Paste the schema (and one sentence per table saying what a row is); 2. Ask for assumptions (listed before any SQL); 3. Read twenty rows (the rows, not the count); 4. Compare row counts (against the source table); 5. Check one row by hand (and rehearse any write as a SELECT). The dangerous output is not the query that errors: that looks after itself. It is the clean, reasonable-looking number returned on the first try. Every step here is cheap, and each one converts an invisible guess into something you can check.\n\n## The rule for anything that writes\n\nNever run a generated `UPDATE` or `DELETE` directly. Run the WHERE clause as a SELECT first, and look at what would be affected:\n\n```sql\n-- Before: see exactly what the generated statement would touch\nSELECT count(*), min(id), max(id) FROM orders WHERE status = 'pendng';\n\n-- If you must run it, run it where you can take it back\nBEGIN;\nUPDATE orders SET status = 'pending' WHERE status = 'pendng';\n-- inspect, then:\nROLLBACK;   -- or COMMIT, once you are sure\n```\n\nIn that example the typo is in the data, not the statement, and the SELECT would have returned 3 rows instead of the 40,000 you feared, or zero when you expected thousands. Either way you learn it before anything changes.\n\n## The asymmetry worth remembering\n\nSQL that fails is free. You see an error, you fix it, nobody is harmed. SQL that succeeds and is wrong costs you a decision, and it costs it silently, sometimes months later.\n\nSo spend your scepticism where the risk actually is. Not on the queries that error (those look after themselves) but on the ones that return a clean, reasonable-looking number on the first try. That is the output you should stare at hardest, whether a model wrote it or you did."},"check":{"question":"An AI writes a query joining orders to payments and reports total revenue of 42.1 lakh rupees. It runs without error and the figure is in a believable range. What is the strongest reason to still not trust it?","options":[{"text":"Models are trained on older material, so it may have used syntax that is deprecated in your database version","correct":false,"tempting":"Worries about staleness, which produces loud errors rather than quietly wrong totals."},{"text":"An order with more than one payment attempt is duplicated by the join, inflating the total in a way that still looks believable","correct":true},{"text":"The query may have used a poor plan and computed the total over a partial scan of the table","correct":false,"tempting":"Confuses slowness with incorrectness; a database returns the complete result or an error, never a partial sum."},{"text":"Money should be aggregated in application code rather than in SQL, where rounding is harder to control","correct":false,"tempting":"Distrusts SQL for financial arithmetic in general instead of examining what this particular join did to the rows."}]},"prev":{"slug":"right-vs-useful","title":"A right number and a useful number are not the same","url":"https://addaly.com/learn/data-and-sql/right-vs-useful"},"next":{"slug":"joins-that-multiply","title":"Joins: reason about them, do not memorise them","url":"https://addaly.com/learn/data-and-sql/joins-that-multiply"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Asking an AI for SQL without being lied to', in Data, SQL and Getting to the Answer. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/data-and-sql/sql-from-an-ai (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Asking an AI for SQL without being lied to","courseTitle":"Data, SQL and Getting to the Answer","module":{"index":1,"slug":"tables-types-and-the-question","title":"Tables, types and the question you are actually asking"},"answer":null,"keyIdea":"Paste your schema, ask for assumptions first, and check the number against something you already know.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"flow","steps":[{"label":"Paste the schema","detail":"and one sentence per table saying what a row is"},{"label":"Ask for assumptions","detail":"listed before any SQL"},{"label":"Read twenty rows","detail":"the rows, not the count"},{"label":"Compare row counts","detail":"against the source table"},{"label":"Check one row by hand","detail":"and rehearse any write as a SELECT"}],"title":"Getting SQL out of a model without being confidently misled","caption":"The dangerous output is not the query that errors: that looks after itself. It is the clean, reasonable-looking number returned on the first try. Every step here is cheap, and each one converts an invisible guess into something you can check."},"alt":"Figure: Getting SQL out of a model without being confidently misled. Steps: 1. Paste the schema (and one sentence per table saying what a row is); 2. Ask for assumptions (listed before any SQL); 3. Read twenty rows (the rows, not the count); 4. Compare row counts (against the source table); 5. Check one row by hand (and rehearse any write as a SELECT). The dangerous output is not the query that errors: that looks after itself. It is the clean, reasonable-looking number returned on the first try. Every step here is cheap, and each one converts an invisible guess into something you can check."}},"blocks":[{"t":"h2","id":"what-an-ai-is-actually-good-at-here","runs":[{"text":"What an AI is actually good at here"}]},{"t":"p","runs":[{"text":"Be fair to the tool before you are suspicious of it. A language model will write a window function you would have spent twenty minutes looking up. It will translate a query between Postgres and BigQuery dialects. It will explain a query plan you cannot read. It will produce a decent first draft five times faster than you can type one. This is real and you should use it."}]},{"t":"p","runs":[{"text":"What it cannot do is know things it was not told, and its failure mode is not silence. It is confidence."}]},{"t":"h2","id":"give-it-the-schema-every-time","runs":[{"text":"Give it the schema, every time"}]},{"t":"p","runs":[{"text":"The most common wrong query from an AI is not a logic error. It is a guess about your column names, dressed as knowledge. It will write "},{"text":"orders.total_amount","code":true},{"text":" when your column is "},{"text":"amount_cents","code":true},{"text":". It will invent a "},{"text":"users.last_login_at","code":true},{"text":" because most schemas have one."}]},{"t":"p","runs":[{"text":"So paste the actual definitions. "},{"text":"\\d orders","code":true},{"text":" in psql, or the CREATE TABLE statements, plus one sentence per table saying what a row means:"}]},{"t":"quote","runs":[{"text":"Here are three tables. One row of "},{"text":"payments","code":true},{"text":" is one attempted charge, including failures. One row of "},{"text":"orders","code":true},{"text":" is one basket someone checked out. An order can have several payment attempts."}]},{"t":"p","runs":[{"text":"That last sentence (the one about several payment attempts) prevents more wrong queries than anything else you can write, because it is exactly the fact that produces row multiplication in a join."}]},{"t":"h2","id":"ask-for-the-assumptions-before-the-sql","runs":[{"text":"Ask for the assumptions before the SQL"}]},{"t":"p","runs":[{"text":"A useful prompt shape:"}]},{"t":"quote","runs":[{"text":"Before writing the query, list what you are assuming about the schema and about the definitions of the terms I used. Then write the query."}]},{"t":"p","runs":[{"text":"This converts invisible guesses into a list you can check in ten seconds. It will say things like “I assumed ‘active’ means a login in the last 30 days” and “I assumed refunds are stored as negative amounts in the same table.” One of those is usually wrong, and it is much cheaper to catch it there than in the output."}]},{"t":"h2","id":"verify-like-you-would-verify-a-stranger-s-work","runs":[{"text":"Verify like you would verify a stranger’s work"}]},{"t":"p","runs":[{"text":"Because that is what it is."}]},{"t":"p","runs":[{"text":"Run it with a LIMIT first and read the rows.","b":true},{"text":" Not the count. The rows."}]},{"t":"p","runs":[{"text":"Check the row count against what you expect.","b":true},{"text":" If the orders table has 40,000 rows and the joined result has 61,000, a join is multiplying, and the AI’s total revenue is now wrong in a way that looks entirely plausible. This is the single most dangerous failure in AI-written SQL: the result is not absurd, it is just too big by 40%."}]},{"t":"p","runs":[{"text":"Check one row by hand.","b":true},{"text":" Pick a customer, look them up in the source, verify their number."}]},{"t":"p","runs":[{"text":"Compare a total to something you already know.","b":true},{"text":" Last month’s figure, the finance export, anything."}]},{"t":"p","runs":[{"text":"Break it on purpose.","b":true},{"text":" Change a filter so the number should move. If it does not move, the filter is not doing anything."}]},{"t":"h2","id":"the-rule-for-anything-that-writes","runs":[{"text":"The rule for anything that writes"}]},{"t":"p","runs":[{"text":"Never run a generated "},{"text":"UPDATE","code":true},{"text":" or "},{"text":"DELETE","code":true},{"text":" directly. Run the WHERE clause as a SELECT first, and look at what would be affected:"}]},{"t":"code","lang":"sql","label":"SQL","text":"-- Before: see exactly what the generated statement would touch\nSELECT count(*), min(id), max(id) FROM orders WHERE status = 'pendng';\n\n-- If you must run it, run it where you can take it back\nBEGIN;\nUPDATE orders SET status = 'pending' WHERE status = 'pendng';\n-- inspect, then:\nROLLBACK;   -- or COMMIT, once you are sure"},{"t":"p","runs":[{"text":"In that example the typo is in the data, not the statement, and the SELECT would have returned 3 rows instead of the 40,000 you feared, or zero when you expected thousands. Either way you learn it before anything changes."}]},{"t":"h2","id":"the-asymmetry-worth-remembering","runs":[{"text":"The asymmetry worth remembering"}]},{"t":"p","runs":[{"text":"SQL that fails is free. You see an error, you fix it, nobody is harmed. SQL that succeeds and is wrong costs you a decision, and it costs it silently, sometimes months later."}]},{"t":"p","runs":[{"text":"So spend your scepticism where the risk actually is. Not on the queries that error (those look after themselves) but on the ones that return a clean, reasonable-looking number on the first try. That is the output you should stare at hardest, whether a model wrote it or you did."}]}],"check":{"question":[{"text":"An AI writes a query joining orders to payments and reports total revenue of 42.1 lakh rupees. It runs without error and the figure is in a believable range. What is the strongest reason to still not trust it?"}],"options":[{"text":[{"text":"Models are trained on older material, so it may have used syntax that is deprecated in your database version"}],"correct":false,"tempting":[{"text":"Worries about staleness, which produces loud errors rather than quietly wrong totals."}]},{"text":[{"text":"An order with more than one payment attempt is duplicated by the join, inflating the total in a way that still looks believable"}],"correct":true,"tempting":null},{"text":[{"text":"The query may have used a poor plan and computed the total over a partial scan of the table"}],"correct":false,"tempting":[{"text":"Confuses slowness with incorrectness; a database returns the complete result or an error, never a partial sum."}]},{"text":[{"text":"Money should be aggregated in application code rather than in SQL, where rounding is harder to control"}],"correct":false,"tempting":[{"text":"Distrusts SQL for financial arithmetic in general instead of examining what this particular join did to the rows."}]}],"order":[2,0,3,1],"why":null},"prev":{"slug":"right-vs-useful","title":"A right number and a useful number are not the same","minutes":9},"next":{"slug":"joins-that-multiply","title":"Joins: reason about them, do not memorise them","minutes":9}}}