得票最多的候选人:先在投票表里聚合
LeetCode 574 · 分组计数、Top 1、先聚合再关联
面试怎么回答
先在投票表按 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 分组,重名会被合并。