【Oracle】淺析 用SQL求第K大問題

一,問題提出

問題:已知學生成績表su,包含id,score兩個字段,現需要取出成績第二高的記錄。
建表語句:

-- oracle
CREATE TABLE  sc  (
   id  NUMBER,
   score  NUMBER 
) 
-- mysql
CREATE TABLE `sc` (
  `id` INT(11) DEFAULT NULL,
  `score` INT(11) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=gbk
`sc`

數據插入:

--oracle
INSERT INTO  sc  VALUES ('1', '43');
INSERT INTO  sc  VALUES ('2', '77');
INSERT INTO  sc  VALUES ('3', '86');
INSERT INTO  sc  VALUES ('4', '66');
INSERT INTO  sc  VALUES ('5', '25');
INSERT INTO  sc  VALUES ('6', '46');
INSERT INTO  sc  VALUES ('7', '77');
--mysql
INSERT INTO `sc` VALUES ('1', '43');
INSERT INTO `sc` VALUES ('2', '77');
INSERT INTO `sc` VALUES ('3', '86');
INSERT INTO `sc` VALUES ('4', '66');
INSERT INTO `sc` VALUES ('5', '25');
INSERT INTO `sc` VALUES ('6', '46');
INSERT INTO `sc` VALUES ('7', '77');

二,求解方法

1,在Oracle中

這種問題,對於Oracle來說是小菜一碟,使用排名的分析函數就可以解決,但是要注意考慮有多個第K大的情況。 排名相關的分析函數有三個row_number、rank、dense_rank。這三個函數的使用情況並不相同,簡單說下,詳細用法請自度娘。
1,row_number:返回連續的排序,無論值是否相等。
2,rank:具有相等值得行排序相同,序數值隨後跳躍。
3,dense_rank:具有相等值得行排序相同,序號是連續的。
舉個例子:
row_number : 對於相同的77,採用連續的序號。

id score rn
3	86	1
7	77	2
2	77	3
4	66	4
6	46	5
1	43	6
5	25	7

rank:對於相同的77,採用相同的序號2,但是66的序號並不是3,而是4。這是因爲rank的序號是跳躍,第二個77的序號其實是3的,也就是序號跳了一位。

id score rn
3	86	1
7	77	2
2	77	2
4	66	4
6	46	5
1	43	6
5	25	7

dense_rank: 對於相同的77,採用相同的序號2,但是序號不會因爲相同數據而進行跳躍,所以66的序號是3。

id score rn
3	86	1
7	77	2
2	77	2
4	66	3
6	46	4
1	43	5
5	25	6

針對於考慮重複值的第K大問題,應該使用dense_rank,如果採用rank,則序號就會有跳躍,結果不正確。
完整sql代碼:

select 
t.id,
t.score
from (
select 
    ID,
    score,
    DENSE_RANK() over(order by score desc ) rn
from sc
) t
where t.rn=2   --第二大,所以取序號爲2的

2,在MySQL中

對於MySQL,如果MySQL的版本<8.0,是使用不了Oracle中的三個排序分析函數的。所以就要有一個通用的方法來解決這種類似的問題,畢竟每種數據庫所包含的函數是不同的,函數不一定通用,但是原始的SQL語法應該是一致的。
在MySQL中的方法中,最先想到的應該是類似以下這種寫法(網上最多的):

SELECT id, MAX(score) score 
FROM sc 
WHERE score < (SELECT MAX(score) FROM sc )

看上去很簡潔,但是有兩個問題:
1,查詢出來的id,其實並不是我們所想的那樣是最大score這條記錄對應的id。大家可以動手運行下,我最開始也是這麼認爲。
2,對於用重複情況時,這條語句沒法全部查詢出來。

3,通用的語句

那麼有沒有針對兩個數據庫,並且都滿足要求的語句呢?方法應該有許多,以下語句大家可以參考下:

SELECT 
*
FROM 
sc
WHERE score = 
(SELECT
t1.score
FROM sc t1
JOIN sc t2 ON t1.score <= t2.score    
GROUP BY t1.score
HAVING COUNT(DISTINCT t2.score) = 2)   -- 以取第二大的爲例子

如果有更好的方法,評論留言!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章