場景
有一張得分表(score),記錄了用戶每次的得分,同一個人可能有多個得分。
id | name | score |
---|---|---|
1 | tom | 45 |
2 | jack | 78 |
3 | tom | 34 |
. | . | . |
需求:找出分數最高的前5個人。
SQL1
首先我們寫個最簡單的sql:
select
id, name, score
from
score
order by
score desc
limit 5;
如果sql這樣寫,結果可能是:
id | name | score |
---|---|---|
2 | jack | 78 |
1 | tom | 45 |
3 | tom | 34 |
排序了,但是沒有去重
SQL2
那么我們加上去重:
select
distinct name
from
score
order by
score desc
limit 5;
首先第一點是這個sql未必能執行。在一些數據庫版本,這個sql可以被執行,在一些版本則會提示你order by的字段必須在distinct中存在(見SQL3)。
但是即使能執行,這個sql也得不到預期結果。原因是distinct優先於order by 被數據庫執行。
在執行distinct name的時候,如上文中的數據。是取id=1的數據,還是id=3的數據呢?其實這是數據庫自行決定的。因此,可能會不正確選擇數據。
比如真的執行這個sql,可能去重的結果是:
id | name | score |
---|---|---|
2 | jack | 78 |
3 | tom | 34 |
然后再執行一個order by,就會認為第一名是jack78分,第二名是tom34分。然而其實tom應該是45分,這個45分就在數據庫執行distinct的時候被錯誤的丟棄了,畢竟先執行distinct的時候不知道你到底要哪個數據。
SQL3
那么我們把score加入select中呢?
select
distinct name, score
from
score
order by
score desc
limit 5;
很明顯,這樣寫的執行結果和我們預期不符。因為如果寫:distinct name,score實際上是對name和score一起去重。比如name都是jack,score都是45。那么這行就會被去掉。
但是問題是正因為把score當做去重的條件了。所以對於同名的人,比如都叫tom,會因為其有兩個分數,導致不能被去重,從而保留兩行記錄。結果就是好像沒有去重。
SQL4
那我不用distinct,用group by進行去重可以嗎?
select
name
from
score
group by
name
order by
score desc
limit 5;
也不行,因為在group by的時候,數據庫還是不知道對兩行name一樣的數據,究竟應該留下哪一行。
SQL5
正確的寫法:
select
name
from
score
group by
name
order by
max(score) desc
limit 5;
這樣寫,在執行group by的時候,數據庫就知道要保留score最大的那一行了。