订阅更新

得票最多的候选人:先在投票表里聚合

SQL 实战 123 题 · 第 21 题 中等 约 2 分钟读完

LeetCode 574 · 分组计数、Top 1、先聚合再关联

本文目录
  1. 面试怎么回答
  2. 题意
  3. 表结构
  4. 思路
  5. 参考解法
  6. 易错点

面试怎么回答

先在投票表按 CandidateId 计数、排序取第一,再关联候选人表拿名字。先聚合再关联,数据量小,也避免了 LEFT JOIN 后 COUNT(*) 把零票候选人算成一票、按名字分组合并重名这两个坑。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

题意

候选人表有 ID 和姓名,投票表每行是一张选票,记录投给了哪个候选人。找出得票最多的候选人姓名(题目保证只有一个)。

表结构

  • Candidate:id(主键)、Name
  • Vote:id(主键,自增)、CandidateId

思路

票都在投票表里,先在投票表按 CandidateId 计数、取票数最多的那个 ID,再回候选人表拿名字。原文第一种写法先 LEFT JOIN 再计数,有两个问题:没得票的候选人会被 COUNT(*) 算成 1 票;按 Name 分组,重名候选人会被合并。

参考解法

写法 1:先关联再计数(有缺陷,见易错点)

select 
    Name
from Candidate c
    left join Vote v
    on c.id = v.CandidateId
    group by Name
    order by count(*) desc
    limit 1

写法 2:先在投票表里统计,再取名字

select Name
from Candidate
where id =
(
select CandidateId
    from    
        (
        select CandidateId, 
        count(*) over(partition by CandidateId ) cnt
        from Vote
        order by cnt desc 
        limit 1
        )t1
)

另一种写法:最直接的写法:投票表聚合取 Top 1,再关联名字

SELECT c.Name
FROM Candidate c
JOIN (
  SELECT CandidateId FROM Vote
  GROUP BY CandidateId
  ORDER BY COUNT(*) DESC
  LIMIT 1
) w ON c.id = w.CandidateId

易错点

  • LEFT JOIN 后用 COUNT(*),0 票的候选人也会计成 1;要用 COUNT(v.id)。
  • 按 Name 分组而不是按 id 分组,重名会被合并。
刷题进度保存在当前浏览器