SQL (PostgreSQL, MySQL, SQLite) Module 18 – Using SQL from application code
Reviewing SQL written by AI assistants
Review SQL from AI coding assistants: the mistakes they make most, checks with fixtures and query plans, and safe rules for running generated SQL.
What you will learn
- Recognise common errors in generated SQL
- Verify a generated query with tests, plans and edge cases
- Set safe rules for running generated SQL
Before you start
What you’ll learn · Before you start
What you will learn
- Recognise common errors in generated SQL
- Verify a generated query with tests, plans and edge cases
- Set safe rules for running generated SQL
Before you start
On this page
AI coding assistants write SQL that almost always runs and usually looks right, which is exactly why it needs a review: their mistakes return wrong rows, not errors. Reviewing SQL written by AI assistants takes four checks. Read the query against your schema and your engine’s dialect. Run it on a small fixture whose answer you know, with the awkward rows in it: NULLs, ties, empty groups. Read its plan if it will run on large tables. And run it safely: on a read-only connection while you explore, and any change inside a transaction whose row count you check before committing. An assistant has seen a lot of SQL, but it has not seen your data, your constraints or your engine version, and those decide whether a plausible query is a correct one.
Why generated SQL needs a review
In the 2025 Stack Overflow Developer Survey, 84 % of respondents said they use AI tools in their development work or plan to, and more of them distrust the accuracy of those tools (46 %) than trust it (33 %). The most common frustration, named by 66 %, was answers that are almost right but not quite. SQL is a language where almost right is easy to miss: a query that drops one customer or counts one order twice still returns a neat table with plausible numbers. Nothing fails, and the error reaches a report or an invoice.
Treat a generated query like a pull request from a capable colleague who has never seen your database. The colleague knows the language well; you know what the data looks like, which columns can be NULL, which joins multiply rows and which engine runs it.
Six mistakes to look for
- NOT IN with NULLs:
x NOT IN (subquery)returns no rows at all once the subquery yields a NULL. - Join fan-out: joining two one-to-many tables at once multiplies rows, so sums and counts grow.
- The wrong dialect:
TOPbelongs to SQL Server,LIMITto PostgreSQL, MySQL and SQLite, and||means OR in MySQL by default. - Integer division: dividing two integers truncates, so a share of 1 in 3 becomes 0.
- Time-zone boundaries: grouping UTC timestamps by date counts late-evening orders in India on the previous day.
- Missing tie-breakers:
ORDER BY ... LIMITwithout a unique final key keeps or drops tied rows arbitrarily.
The first two hide in queries about absence and totals, which is where assistants are asked most often. The last four depend on facts about your engine and your data that the question rarely states, so say them in the question: the engine and version, which columns can be NULL, the time zone of the report and what a tie should mean.
Four checks between a generated query and real data
Text description of the diagram
The figure shows a review routine as one column, from top to bottom.
It starts with your question, written in one sentence with its edge cases named, and the SQL the assistant generated for it. Then come four checks:
- Read it: are the schema, the dialect, the joins and the handling of NULL right?
- Run it on a fixture with answers you know.
- Read its plan on tables of real size.
- Run it safely: on a read-only connection, or as a counted write inside a transaction.
When all four pass, use the query and keep the fixture as its test. A dashed arrow back to the generated SQL says that when any check fails, you fix the query or the question and go through the checks again.
One bug, caught by a fixture
The question was “which customers have never placed an order?”, and the shop allows guest checkouts, which are orders
without a customer. The assistant’s answer used NOT IN:
-- The question: which customers have never placed an order? Guest checkouts are orders with no customer.
CREATE TABLE customers (customer_id INTEGER PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE orders (order_id INTEGER PRIMARY KEY, customer_id INTEGER REFERENCES customers);
INSERT INTO customers VALUES (1, 'Asha Kulkarni'), (2, 'Vikram Nair'), (3, 'Meera Rathore'), (4, 'Tenzin Dorjee');
INSERT INTO orders VALUES (101, 1), (102, 1), (103, 3), (104, NULL);
-- An assistant's answer: plausible, runs without an error. How many customers does it find?
SELECT count(*) AS customers_found FROM (
SELECT name FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM orders)
);
-- Why it returned nothing: the NULL of the guest order makes NOT IN unknown for every customer.
SELECT 2 NOT IN (1, 3) AS without_null, 2 NOT IN (1, 3, NULL) AS with_null;
-- The fix: NOT EXISTS asks "is there a matching row?", which a NULL cannot confuse.
SELECT c.name AS never_ordered FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id)
ORDER BY c.name; Output
┌─────────────────┐ │ customers_found │ ├─────────────────┤ │ 0 │ └─────────────────┘ ┌──────────────┬───────────┐ │ without_null │ with_null │ ├──────────────┼───────────┤ │ 1 │ │ └──────────────┴───────────┘ ┌───────────────┐ │ never_ordered │ ├───────────────┤ │ Tenzin Dorjee │ │ Vikram Nair │ └───────────────┘
Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < never_ordered.sql
Runs on this device, in your browser. The first run downloads SQLite (about 0.7 MB), which is kept for the next runs.
Your run, in this browser
The fixture has four customers, two of whom never ordered, and one guest order whose customer_id is NULL. The
generated query found no one. The second statement shows why: 2 NOT IN (1, 3) is true, but 2 NOT IN (1, 3, NULL) is
unknown, because 2 might equal the unknown value, and WHERE keeps only rows whose condition is true. One NULL in the
subquery therefore empties the result. NOT EXISTS asks whether a matching row exists, and found Tenzin Dorjee and
Vikram Nair. Without the guest order in the fixture, both queries would have returned the same two names, and the bug
would have waited for the first guest checkout in production.
Check yourself
3 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Five more mistakes in assistant-style SQL, reproduced and fixed Pro: opens with a Pro, Premium or Ultimate pass
Each of these queries runs, returns a tidy result and is wrong for a reason that only the data or the engine reveals. Here they run on one fixture whose answers can be checked by hand, next to their fixes.
In this part:
- Fan-out, integer division, a lost LEFT JOIN and a tie, run
- The dialect check
- Time zones, the mistake that hides in the date
2 runnable examples (SQL) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
A review routine: fixtures, plans and safe execution Pro: opens with a Pro, Premium or Ultimate pass
Reading catches some mistakes; the rest need the query to run. This part reads a generated query’s plan, runs a generated change behind two safety rails, and has you fix a generated report.
In this part:
- Reading the plan of a generated query
- Two safety rails for running generated SQL
- Asking for SQL that is easy to review
- Your turn: fix a generated report
- Fix two bugs in a generated report query
- Results of the sample tests
2 runnable examples (SQL, mart.py, safe_run.py) · 1 exercise with sample tests · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Mid to senior
A product manager used an AI assistant to write "revenue per category for last month" and put the query on a dashboard. The numbers look plausible but are 18 % above the finance team's. How do you review the query to find the cause, and how do you make sure the fixed version is right?
-
Mid to senior
A developer asked an assistant to "delete the test accounts created last week" and ran its DELETE on production. About 30 rows should have gone; 40,000 did. How can a plausible DELETE do that, how do you recover, and what rules would have stopped it?
-
Senior to staff
Your team wants to use AI assistants to write SQL for reports, ad hoc fixes and schema migrations. As the lead, what rules do you set, how do you enforce them, and what do you leave open?
Key takeaways
- Generated SQL fails silently: review it against your schema, your data and your engine, not by how it reads.
- Look first for NOT IN with NULLs, fan-out, the wrong dialect, integer division, time-zone dates and missing tie-breakers.
- Explore read-only, and run every change in a transaction whose row count you check.
References
- 2025 Stack Overflow Developer Survey: AI (Stack Overflow)
- SQLite: The SELECT statement (SQLite)
- SQLite: SQL Language Expressions (IN and NOT IN with NULL) (SQLite)
- SQLite: EXPLAIN QUERY PLAN (SQLite)
- SQLite: URI filenames (mode=ro) (SQLite)
- PostgreSQL documentation: Using EXPLAIN (The PostgreSQL Global Development Group)
- PostgreSQL documentation: Subquery Expressions (NOT IN and NULL) (The PostgreSQL Global Development Group)
- PostgreSQL documentation: SET TRANSACTION (READ ONLY) (The PostgreSQL Global Development Group)
- PostgreSQL documentation: GRANT (The PostgreSQL Global Development Group)
- MySQL 8.4 Reference Manual: Server SQL Modes (PIPES_AS_CONCAT) (Oracle Corporation)
- Transact-SQL reference: || (String Concatenation) (Microsoft)
- Transact-SQL reference: TOP (Microsoft)
- sqlite3: DB-API 2.0 interface for SQLite databases (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress