{"id":"data2-sql-conversion-by-channel-distinct","audience":"analysts","category":"data","track":"data","technique":"constraints","level":"advanced","title":"Write SQL for conversion rate by channel without double counting","free":false,"task":"Marketing wants October 2025 conversion by channel: the share of visitors who placed a paid order on the same day as a session in that channel. Ask for one Postgres query in a single code block with at most 35 words outside it. Count people not sessions or orders, keep channels with no buyers, round the percent to one decimal, and sort highest first.","materialLabel":"Schema and notes (attached to your prompt automatically)","material":"Schema (Postgres) - Fernhill Foods web analytics\n\nsessions(session_id, user_id, channel, session_date)\norders(order_id, user_id, order_date, status, total_cents)\n  status values: 'paid', 'refunded', 'cancelled'\n\nNotes:\n- channel values: 'search', 'email', 'social', 'direct'.\n- A user can have many sessions in a channel in October and can place several orders on one day.\n- A visitor converts in a channel if they have a paid order with order_date equal to the session_date of one of their sessions in that channel.\n- Conversion % = converting users / distinct visitors in that channel x 100.\n- October 2025 = session_date from 2025-10-01 up to but not including 2025-11-01.\n- A user who browsed on a channel but never bought still counts as a visitor in that channel.\n- Last time a teammate counted sessions and orders directly, a heavy buyer with 5 orders in one day inflated the social rate above 100%.\n- Columns: channel, visitors, buyers, conversion_pct.","rules":[{"type":"codeBlocks","count":1},{"type":"maxProseWords","max":35}],"mistakes":[{"title":"Counting sessions and orders","why":"The AI divides order rows by session rows and gets rates above 100%.","fix":"Add: \"COUNT(DISTINCT user_id) on both sides.\"","checks":["r0"]},{"title":"Dropping non-buyers","why":"The AI inner joins or puts the paid filter in WHERE.","fix":"Add: \"LEFT JOIN with status = 'paid' in the ON clause.\"","checks":["r1"]},{"title":"Integer division","why":"The percent comes out as 0.","fix":"Add: \"100.0 * buyers / visitors, ROUND to 1 decimal.\"","checks":["r2"]},{"title":"Leaving the format to chance","why":"The AI picks its own length and layout unless told the exact shape.","fix":"Add: \"Put the code in exactly 1 code block. Keep the writing outside the code under 35 words.\"","checks":["codeBlocks","maxProseWords"]}],"promptHints":[{"label":"Says count distinct","patterns":["distinct"]},{"label":"Says left join and ON","patterns":["left join","on clause","in the on"]},{"label":"Gives the date filter","patterns":["2025-10-01","2025-11-01"]},{"label":"Gives the percent math","patterns":["100\\.0","round"]},{"label":"Caps the writing","patterns":["one (short )?sentence","\\d+ words","code block"]}],"hint":"Conversion queries double count unless both sides use DISTINCT. Say it, and say where the paid filter goes.","mission":"Write SQL for conversion by channel.","musts":["Distinct people","Paid filter in ON","Percent rounded"],"rubricLabels":["The query LEFT JOINs orders to sessions on user_id and order_date = session_date, with o.status = 'paid' in the ON clause (not WHERE), so visitors who never bought still appear","visitors is COUNT(DISTINCT s.user_id) and buyers is COUNT(DISTINCT o.user_id), so people are counted once regardless of sessions or orders","conversion_pct is 100.0 * buyers / visitors rounded to one decimal without integer division, October 2025 is filtered on session_date, and results are grouped by channel and ordered by conversion_pct descending"]}