CMD Guide
HomeDatabasesSQL Practice Problems

Team Scores in Football Tournament

Mechanism

Each match contributes points to two rows, so you fan one Matches row out to both participants with a LEFT JOIN on team_id = host_team OR team_id = guest_team, then collapse the fan-out with SUM(CASE …) that reads the match from whichever side (host or guest) the team played, awarding 3 / 1 / 0. The LEFT side keeps teams that never played (their only joined row is all-NULL, so the CASE falls through to 0).

Schema and the canonical query

Teams(team_id PK, team_name) and Matches(match_id PK, host_team, guest_team, host_goals, guest_goals). Every finished match is one row holding both teams and both scores; nobody's total lives in one place, which is exactly why a plain join won't do.

SELECT t.team_id,
       t.team_name,
       SUM(CASE
             WHEN t.team_id = m.host_team  AND m.host_goals  > m.guest_goals THEN 3
             WHEN t.team_id = m.guest_team AND m.guest_goals > m.host_goals  THEN 3
             WHEN m.host_goals = m.guest_goals                              THEN 1
             ELSE 0
           END) AS num_points
FROM   Teams t
LEFT JOIN Matches m
       ON t.team_id = m.host_team
       OR t.team_id = m.guest_team
GROUP BY t.team_id, t.team_name
ORDER BY num_points DESC, t.team_id ASC;

Note the third WHEN: by the time control reaches it, the team is provably a participant (it survived the join) and neither win-branch fired, so host_goals = guest_goals alone correctly catches the draw for either side. For a team with no matches, m.host_goals is NULL; every comparison is NULL (not true), so the CASE returns 0 and SUM of a single 0 is 0 — the team still appears.

Worked example with real values

Five teams; Chicago (40) plays no match. Five matches:

match_idhost_teamguest_teamhost_goalsguest_goalsresult
110203010 wins
2301022draw
310505110 wins
420301020 wins
550301050 wins

After the OR-join, every match appears once per participant — match 1 produces one row for team 10 and one for team 20, never two identical rows for the same team. Here is the exact join output (one line per team-match), with the CASE evaluated:

team_idteam_namematch_idhost_goalsguest_goalsroleCASE pts
10DesignGuru FC130host3
10DesignGuru FC222guest1
10DesignGuru FC351host3
20NewYork FC130guest0
20NewYork FC410host3
30Atlanta FC222host1
30Atlanta FC410guest0
30Atlanta FC510guest0
40Chicago FCNULLNULLNULL0
50Toranto FC351guest0
50Toranto FC510host3

GROUP BY then sums each team's CASE pts:

team_idteam_namesum of ptsnum_points
10DesignGuru FC3+1+37
20NewYork FC0+33
30Atlanta FC1+0+01
40Chicago FC00
50Toranto FC0+33

After ORDER BY num_points DESC, team_id ASC the final result is 10 (7), 20 (3), 50 (3), 30 (1), 40 (0) — the 20-before-50 tie broken by the smaller team_id, and Chicago surviving at 0 because of the LEFT join.

diagram
diagram

Why the naive version is wrong

A tempting shortcut writes the team into two rows itself — one fixed to the host slot, one to the guest slot — by joining twice or by a Cartesian-style condition that doesn't guard which side it scores. If the CASE then doesn't pin the comparison to the team's actual role, a single match can be counted twice for the same team, inflating points (the symptom: a phantom duplicate join row like 20 | 20 30 | 1 0 appearing twice for team 20, which the OR-join never produces — each match matches a given team on exactly one of the two OR arms). Always tie the win-test to the role: team_id = host_team AND host_goals > guest_goals, never just host_goals > guest_goals.

The UNION ALL alternative (and why it's often faster)

The OR in a join predicate is the expensive part: most planners cannot turn a = x OR a = y into a single index seek, so it degrades toward a nested-loop or hash join that touches the whole table. Splitting the two roles into a UNION ALL gives the optimizer two clean equi-joins, each index-friendly:

SELECT t.team_id, t.team_name,
       COALESCE(SUM(s.pts), 0) AS num_points
FROM Teams t
LEFT JOIN (
    SELECT host_team AS team_id,
           CASE WHEN host_goals > guest_goals THEN 3
                WHEN host_goals = guest_goals THEN 1 ELSE 0 END AS pts
    FROM Matches
    UNION ALL
    SELECT guest_team AS team_id,
           CASE WHEN guest_goals > host_goals THEN 3
                WHEN guest_goals = host_goals THEN 1 ELSE 0 END AS pts
    FROM Matches
) s ON s.team_id = t.team_id
GROUP BY t.team_id, t.team_name
ORDER BY num_points DESC, t.team_id ASC;

It must be UNION ALL, not UNION: a 3-0 win and another 3-0 win are identical (team_id, pts) rows and UNION would dedup them, silently dropping points. The COALESCE replaces the all-NULL sum for teams with no matches.

Pitfalls

Takeaways


Problem: LeetCode 1212 “Team Scores in Football Tournament”. Mechanics of conditional aggregation, the OR-join planner cost, and the UNION ALL rewrite cross-checked against the PostgreSQL documentation (join methods, UNION/UNION ALL set semantics) and standard SQL CASE/NULL three-valued-logic behavior. Re-authored and deepened for this guide: the original Step-1 intermediate table contained a fabricated phantom duplicate row for team 20 that the OR-join cannot produce; replaced with a fully self-consistent five-team / five-match dataset, an end-to-end traced join-and-aggregate, a fan-out diagram, the ‘why the naive double-count is wrong’ note, and the UNION ALL alternative with its cost rationale.

🤖 Don't fully get this? Learn it with Claude

Stuck on Team Scores in Football Tournament? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Team Scores in Football Tournament** (Databases) and want to truly understand it. Explain Team Scores in Football Tournament 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.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Team Scores in Football Tournament** 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.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Team Scores in Football Tournament** 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.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Team Scores in Football Tournament** 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.

📝 My notes