目录

题目描述

1212. 查询球队积分

题意分析

要的是一张「每支球队的总积分」表,输出三列:team_idteam_namenum_points,按积分降序、球队编号升序排列。

计分规则由两张表共同决定:Matches 的一行记录里,主队和客队各自要根据比分拿分,胜 3 分、平 1 分、负 0 分。也就是说同一行比赛要对两支不同球队产生贡献,一支球队又可能出现在 host_teamguest_team 任意一侧。

约束里最关键的一句是「Teams 里的球队可能一场比赛都没打」。这句话直接决定了必须保留没有比赛记录的球队,并且把它的积分补成 0 而不是 NULL,这是本题唯一真正的坑。

边界:球队零场比赛、积分并列、比分为 0 比 0 的平局、Matches 整表为空。

解法:SQL 查询建模

核心思路

一场比赛横向存着主队和客队,但最终要按球队聚合。先用 UNION ALL 把每场比赛规范化成两行 (team_id, points):主队一行、客队一行,各自用 CASE 根据比分计算 3、1、0 分。后续只面对统一的「某队在某场比赛得到多少分」记录,可以用等值连接和普通求和完成统计。

规范化后的不变量是:每场比赛恰好贡献两行,而且每行积分恰好等于对应球队在该场比赛的得分。因此按 team_id 求和,会完整计入该队所有主场和客场积分,也不会重复或漏算。这里必须用 UNION ALL;若用 UNION,同一球队多场比赛得到相同分数的记录会被去重。

最外层以 Teams 为主表做 LEFT JOIN,保证零场球队仍有一行;这类球队的 SUM(p.points)NULL,用 COALESCE(..., 0) 转成题目要求的 0。最后按完整规则排序:积分降序,积分相同时球队编号升序。

解题步骤

  • 第一个子查询输出主队积分:主队进球更多得 3 分,相等得 1 分,否则得 0 分。
  • 第二个子查询对客队做对称计算,用 UNION ALL 保留所有比赛贡献。中间表每行都统一为 (team_id, points)
  • Teams 左连接积分明细,按 team_id, team_name 分组并求和;没有明细的球队用 COALESCE 补 0。
  • num_points DESC, team_id ASC 排序。第二个键处理积分并列,不能省略。

Teams = [(1,'A'),(2,'B'),(3,'C'),(4,'D'),(5,'E')]Matches = [(1,1,2,0,1),(2,2,3,2,2),(3,3,4,1,3)] 为例,规范化后得到 A:0,B:3、1,C:1、0,D:3 六条积分记录。分组求和得到 A=0、B=4、C=1、D=3;E 没有匹配记录,由 COALESCE 得 0。最终顺序是 B(4)、D(3)、C(1)、A(0)、E(0),A 与 E 同分时按较小 team_id 在前。

代码实现

SELECT
    t.team_id,
    t.team_name,
    COALESCE(SUM(p.points), 0) AS num_points
FROM Teams AS 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 points
    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 points
    FROM Matches
) AS p ON p.team_id = t.team_id
GROUP BY t.team_id, t.team_name
ORDER BY num_points DESC, t.team_id ASC;

复杂度分析

  • 时间复杂度:按逻辑数据量计为 $O(M + T \log T)$,其中 $M$ 是比赛数、$T$ 是球队数。两次扫描比赛表产生 $2M$ 条明细,聚合与等值连接处理线性规模的数据,最终对 $T$ 支球队排序。实际代价由索引和执行计划决定。
  • 空间复杂度:最坏 $O(M + T)$;执行器可能物化 $2M$ 条规范化明细,并维护球队聚合与排序状态。

关键点总结

  • 一行比赛同时描述两个实体的贡献时,先用 UNION ALL 规范化成长表,再聚合;统一数据形状后,主客场差异不会泄漏到外层逻辑。
  • UNION ALL 是正确性要求,不只是性能选择:UNION 会把相同的 (team_id, points) 比赛贡献去重。
  • 输出要求覆盖所有球队,所以必须以 Teams 为主表左连接;COALESCE 再把「无明细」转换为 0 分。
  • CASE 的三个分支分别对应胜、平、负,主队和客队的比较方向要对称。
  • 排序键必须完整写出积分降序和球队编号升序,保证并列结果确定。

易错点总结

  • 错误写法:用 UNION 代替 UNION ALL。若同一球队两场都获胜,两条 (team_id, 3) 会被合并成一条,总分从 6 错成 3。
  • 错误写法:客队分支仍写 host_goals > guest_goals THEN 3。比赛 A 0:1 B 时,B 本应得 3 分,却会被算成 0。
  • 错误写法:从积分明细出发或使用 INNER JOIN。示例中的 E 没有比赛记录,会从结果中消失,而题目要求输出 E 的 0 分。
  • 错误写法:省略 COALESCE。零场球队虽然被 LEFT JOIN 保留,但 SUM(NULL) 的结果仍是 NULL,不是 0。
  • 错误写法:只按积分降序。A 与 E 都是 0 分时次序不确定,必须追加 team_id ASC

相似题目

题目 难度 考察点
175. 组合两个表 简单 最小化的 LEFT JOIN,只保行不做聚合
1280. 学生们参加各科测试的次数 简单 需要先 CROSS JOIN 造出全量组合再左连计数
1107. 每日新用户统计 中等 先用子查询取每人首次行为日期,再按日期聚合
1126. 查询活跃业务 中等 聚合结果反过来作为过滤条件,需要两轮聚合