Weather Type in Each Country
Mechanism
The engine joins each Weather row to its Countries row on country_id, keeps only the November-2019 rows, collapses the survivors into one group per country with GROUP BY, computes AVG(weather_state) over each group, and a CASE maps that single average onto a Cold/Warm/Hot label. The whole query is one AVG per country wrapped in a three-way threshold test.
The correct query
SELECT c.country_name,
CASE WHEN AVG(w.weather_state) <= 15 THEN 'Cold'
WHEN AVG(w.weather_state) >= 25 THEN 'Hot'
ELSE 'Warm'
END AS weather_type
FROM Countries c
JOIN Weather w ON w.country_id = c.country_id
WHERE w.day >= '2019-11-01' AND w.day < '2019-12-01'
GROUP BY c.country_id, c.country_name;Two deliberate choices beyond the original answer. First, the date filter lives in WHERE, not in the join's ON — for this INNER JOIN both produce the same rows, but the placement encodes intent and survives a later change to LEFT JOIN (the pitfall below). Second, the predicate is a half-open range day >= '2019-11-01' AND day < '2019-12-01' rather than YEAR(day)=2019 AND MONTH(day)=11. Wrapping the column in YEAR()/MONTH() makes the predicate non-sargable: the engine must call the function on every row and cannot seek an index on day. A bare-column range comparison can use a B-tree index on day. Group by country_id (the key) and carry country_name along; grouping by name alone breaks if two countries share a name.
Why the naive trace is misleading
The original walkthrough invents tables called Step1, Step2, Step3 and selects FROM Step1. Those tables do not exist — you cannot run those snippets. SQL has no such intermediate relations you can name; the optimizer produces internal pipeline stages, not queryable tables. The trace below uses the real evaluation order instead.
Worked trace on the real rows
The logical clause order is FROM/JOIN → WHERE → GROUP BY → aggregate → SELECT/CASE. Every November row already passes the date filter, so after the join + WHERE the engine groups by country and averages:
| country_id | weather_state values (Nov 2019) | sum / count | AVG | test | label |
|---|---|---|---|---|---|
| 2 | 15 | 15 / 1 | 15.000 | ≤ 15 | Cold |
| 3 | -2, 0, 3 | 1 / 3 | 0.333 | ≤ 15 | Cold |
| 5 | 16, 18, 21 | 55 / 3 | 18.333 | 15 < x < 25 | Warm |
| 7 | 25 | 25 / 1 | 25.000 | ≥ 25 | Hot |
| 8 | 25, 27, 31 | 83 / 3 | 27.667 | ≥ 25 | Hot |
Note the boundaries: 15 hits the <= 15 arm (Cold, not Warm) and 25 hits >= 25 (Hot). The CASE is evaluated top-to-bottom and stops at the first true arm, so an average of exactly 15 can never reach the Warm ELSE. Joining country_id back to names gives USA=Cold, Australia=Cold, China=Warm, Peru=Hot, Morocco=Hot.
Pitfalls
- ON vs WHERE is the real lesson. On this INNER JOIN they are interchangeable, which is exactly why it is a trap: the day you change to
LEFT JOINto also list countries with no November readings, a date predicate left inWHEREwill discard those countries (theirw.dayis NULL, andNULL >= '2019-11-01'is unknown, whichWHEREtreats as false). The same predicate inONkeeps them. Decide by intent: filters on the preserved table go inWHERE; filters on the optional table go inON. - Non-sargable date filters.
YEAR(day)=2019 AND MONTH(day)=11forces a full scan because the column is buried in a function. The half-open rangeday >= '2019-11-01' AND day < '2019-12-01'is sargable and can use an index. Prefer< '2019-12-01'overBETWEEN ... '2019-11-30'so you don't miss2019-11-30 14:00timestamps. - Boundary off-by-one in CASE. The spec says ≤15 is Cold and ≥25 is Hot. Writing
< 15or> 25would misclassify the exactly-15 and exactly-25 countries (here country 2 and country 7). The order of the WHEN arms also matters — list the Cold and Hot tests before the WarmELSE. - AVG ignores NULLs, COUNT(*) does not.
AVG(weather_state)divides by the count of non-NULL states, not the row count. If some readings were NULL,SUM/COUNT(*)would give a different (wrong) answer. TrustAVGfor this.
Takeaways
- Aggregation pipeline:
JOIN→WHERE→GROUP BY→AVG→CASEturns many daily rows into one labeled row per country. ONfilters during the join;WHEREfilters after. Identical on INNER JOIN, decisive on outer joins — a right-table filter inWHEREquietly demotes a LEFT JOIN to an INNER JOIN.- Keep date columns bare in predicates (sargable half-open ranges) so the optimizer can use an index instead of scanning.
- Match
CASEboundaries to the spec exactly (≤ and ≥), and group by the key, not a possibly-duplicated name.
Based on LeetCode 1294 “Weather Type in Each Country” and its editorial. The ON-vs-WHERE / outer-join semantics follow the SQL standard's logical clause-evaluation order (FROM/ON → JOIN NULL-padding → WHERE → GROUP BY → SELECT) as described in PostgreSQL and MySQL documentation and in Markus Winand's Use The Index, Luke on sargable predicates. Re-authored and deepened for this guide: corrected the imaginary Step1/Step2/Step3 tables into a real evaluation trace, moved the date filter to a sargable WHERE range, and added the ON-vs-WHERE systems lesson the original omitted.
🤖 Don't fully get this? Learn it with Claude
Stuck on Weather Type in Each Country? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Weather Type in Each Country** (Databases) and want to truly understand it. Explain Weather Type in Each Country from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Weather Type in Each Country** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Weather Type in Each Country** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Weather Type in Each Country** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.