{"id":"code-dev-sql-left-join-october-spend","audience":"developers","category":"coding","track":"coding","technique":"context","title":"Write a LEFT JOIN that keeps customers who spent nothing","free":false,"task":"Marketing wants every customer who isn't deleted, with their spend from paid orders in October 2026, including customers who spent nothing (show 0, not NULL). The output columns must be exactly email and october_spend_dollars (the table stores cents), sorted by october_spend_dollars ascending, then email. Write one PostgreSQL query in exactly 1 code block of at most 15 lines, with under 30 words of explanation.","materialLabel":"The schema and some sample rows (attached to your prompt automatically)","material":"-- PostgreSQL 15, database: lumen_shop\nCREATE TABLE customers (\n  id          SERIAL PRIMARY KEY,\n  email       TEXT NOT NULL UNIQUE,\n  full_name   TEXT,\n  signed_up   DATE NOT NULL,\n  deleted_at  TIMESTAMP NULL       -- set when an account is closed\n);\n\nCREATE TABLE orders (\n  id           SERIAL PRIMARY KEY,\n  customer_id  INT NOT NULL REFERENCES customers(id),\n  placed_at    TIMESTAMP NOT NULL,\n  total_cents  INT NOT NULL,\n  status       TEXT NOT NULL        -- 'paid', 'pending' or 'refunded'\n);\n\n-- sample rows\n-- customers\n-- 1 | ana@lumen.test   | Ana Ruiz    | 2025-03-02 | NULL\n-- 2 | ben@lumen.test   | Ben Okafor  | 2026-07-19 | NULL\n-- 3 | cleo@lumen.test  | Cleo Park   | 2024-11-30 | 2026-09-01 08:00\n-- 4 | dev@lumen.test   | Dev Shah    | 2026-10-05 | NULL\n\n-- orders\n-- 10 | 1 | 2026-10-03 14:22 | 4599 | paid\n-- 11 | 1 | 2026-10-28 09:10 | 1200 | refunded\n-- 12 | 2 | 2026-09-30 23:55 | 3000 | paid\n-- 13 | 2 | 2026-11-01 00:05 | 2500 | paid\n-- 14 | 3 | 2026-10-12 10:00 | 9900 | paid\n-- 15 | 4 | 2026-10-20 18:45 | 1500 | pending\n\n-- existing report query someone started (returns too few rows):\n-- SELECT c.email, SUM(o.total_cents) AS spend\n-- FROM customers c LEFT JOIN orders o ON o.customer_id = c.id\n-- WHERE o.status = 'paid'\n-- GROUP BY c.email;","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":15},{"type":"maxProseWords","max":30}],"mistakes":[{"title":"Filtering in WHERE","why":"A WHERE on the orders table throws away customers with no orders, which quietly turns the LEFT JOIN into an inner join.","fix":"Say: \"Put the paid and October conditions in the ON clause so zero-spend customers stay.\"","checks":["r0"]},{"title":"Leaving NULLs and deleted accounts","why":"The AI returns NULL for no orders and forgets deleted_at unless you say who counts.","fix":"Add: \"Only customers where deleted_at IS NULL; show 0, not NULL.\"","checks":["r1"]},{"title":"Not naming the columns","why":"The AI picks its own names, keeps cents and skips the sort your report needs.","fix":"Add: \"Columns exactly email, october_spend_dollars (cents / 100), ORDER BY october_spend_dollars, email.\"","checks":["r2"]},{"title":"Getting several versions","why":"The AI often gives a query plus 'an alternative' and a long walkthrough.","fix":"Add: \"One query, one code block, no alternatives, at most a sentence of explanation.\"","checks":["codeBlocks","maxCodeLines","maxProseWords"]}],"promptHints":[{"label":"Says who counts","patterns":["deleted","zero","no orders","spent nothing","0"]},{"label":"Names the date range","patterns":["october","2026-10"]},{"label":"Names the output columns","patterns":["october_spend_dollars","column","alias"]},{"label":"Mentions the join trap","patterns":["left join","on clause","not.{0,20}where"]},{"label":"Asks for one query only","patterns":["one query","single query","one code block","no alternatives"]}],"hint":"A LEFT JOIN only keeps zero-spend customers if the filters sit in the right place. Tell the AI who counts, the date range, the exact columns and sort, and that you want one query.","rubricLabels":["Uses a LEFT JOIN from customers to orders with the status = 'paid' and October 2026 date conditions in the ON clause (not in WHERE), so customers with no paid October orders still appear","Excludes deleted customers (deleted_at IS NULL) and shows 0 instead of NULL for customers with no matching orders (e.g. COALESCE(SUM(...), 0))","Returns exactly two columns named email and october_spend_dollars, with cents divided by 100, sorted by october_spend_dollars ascending and then email"]}