經(jīng)典SQL題目-求第N高的薪水的解法匯總及知識(shí)點(diǎn)復(fù)習(xí)

這幾天在看Leetcode的時(shí)候逐步開始留意SQL題目,不做不知道,一做才感覺自己的SQL太弱了,因此將一道經(jīng)典題目:求第N高的薪水的解法進(jìn)行匯總(MySQL)。相關(guān)解法的原文鏈接已標(biāo)注在文末~

題目的鏈接為:第N高的薪水

一、題干

第N高的薪水:編寫一個(gè) SQL 查詢,獲取 Employee 表中第 n 高的薪水(Salary)。

+----+--------+
| Id | Salary |
+----+--------+
| 1  | 100    |
| 2  | 200    |
| 3  | 300    |
+----+--------+

例如上述 Employee 表,n = 2 時(shí),應(yīng)返回第二高的薪水 200。如果不存在第 n 高的薪水,那么查詢應(yīng)返回 null。

+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| 200                    |
+------------------------+

初始化代碼片段。

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.

  );
END

二、前置知識(shí)碎片

簡(jiǎn)單總結(jié),有時(shí)間再詳細(xì)補(bǔ)充知識(shí)點(diǎn)~

1. limit用法

limit子句用于限制查詢結(jié)果返回的數(shù)量。

用法:[select * from tableName limit i,n]

參數(shù)

  • tableName : 為數(shù)據(jù)表;
  • i : 為查詢結(jié)果的索引值(默認(rèn)從0開始);
  • n : 為查詢結(jié)果返回的數(shù)量

2. order by

order by為排序,ASC(默認(rèn)升序)和DESC`(降序)

適用于單列升序、單列降序和多列排序,示例:

SELECT * FROM Websites
ORDER BY alexa DESC;

教程鏈接

3. declare 和 set

[declare 字段名 字段類型]

[set 賦值表達(dá)式]

4. if和ifnull

f(true,a,b), if(false,a,b) 這個(gè)就是第一個(gè)如果是true,就等于a,false就等于b,有點(diǎn)像三元表達(dá)式;

ifnull(a, b) ifnull里有兩個(gè)值,如果a不是null,則返回a, 如果a=null,則返回b。

5. 窗口函數(shù)

實(shí)際上,在mysql8.0中有相關(guān)的內(nèi)置函數(shù),而且考慮了各種排名問題:

  • row_number(): 同薪不同名,相當(dāng)于行號(hào),例如3000、2000、2000、1000排名后為1、2、3、4
  • rank(): 同薪同名,有跳級(jí),例如3000、2000、2000、1000排名后為1、2、2、4
  • dense_rank(): 同薪同名,無跳級(jí),例如3000、2000、2000、1000排名后為1、2、2、3
  • ntile(): 分桶排名,即首先按桶的個(gè)數(shù)分出第一二三桶,然后各桶內(nèi)從1排名,實(shí)際不是很常用

顯然,本題是要用第三個(gè)函數(shù)。另外這三個(gè)函數(shù)必須要要與其搭檔over()配套使用,over()中的參數(shù)常見的有兩個(gè),分別是

  • partition by,按某字段切分
  • order by,與常規(guī)order by用法一致,也區(qū)分ASC(默認(rèn)升序)和DESC(降序)

三、解法

1. 解法一:set賦值+distinct去重+limit取值的單表查詢

題目要求

顯示沒有固定的N高薪水,N的確定由自定義函數(shù)傳入。

解題思路

  • 使用limit
  • limit start, count。其中start的顯示值是從start+1開始的。但limit后面輸入不能是計(jì)算式,比如:N-1
  • 第N高的N,是通過自定義函數(shù)getNthHighestSalary的(N INT)中N傳入。start必須是從N-1開始,才能顯示符合題目要求的結(jié)果。比如第N=2高,如果直接用N值到limit,limit 2,1,意為從第3行開始,顯示一行。所以要用N-1=1,才能表示從第二行開始。
  • 這時(shí),應(yīng)通過一個(gè)替代參數(shù)實(shí)現(xiàn)。MySQL自定函數(shù)中的參數(shù)是靜態(tài)參數(shù),即要先定義后使用。先用declare定義類型,后通過set進(jìn)行賦值。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    declare m INT;
    set m=N-1; 
    RETURN (
        # Write your MySQL query statement below.
        select ifnull(
          (
            select distinct Salary 
            from Employee 
            order by Salary 
            desc limit m,1
          ),
          null
        )
    );
END

注意:此處的set,可以直接set N=N-1,而不用declare m,但聲明m會(huì)更加明晰一些~

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    set N=N-1; 
    RETURN (
        # Write your MySQL query statement below.
        select ifnull(
          (
            select distinct Salary 
            from Employee 
            order by Salary 
            desc limit N,1
          ),
          null
        )
    );
END

2. 解法二:set賦值+group by去重 + limit取值

主體思路同方法一,group by同樣可以起到分組去重的效果,用以代替distinct

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    set N=N-1; 
    RETURN (
        # Write your MySQL query statement below.
        select ifnull(
          (
            select Salary 
            from Employee 
            group by Salary
            order by Salary 
            desc limit N,1
          ),
          null
        )
    );
END

解法一和解法二最為簡(jiǎn)潔直觀,但僅適用于查詢?nèi)峙琶麊栴},如果要求各分組的每個(gè)第N名,則該方法不適用;而且也不能處理存在重復(fù)值的情況。

3. 解法三:連續(xù)排名解法

  • 計(jì)算出每一個(gè)薪水的連續(xù)排名
  • 找到排名等于N的薪水,并輸出
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    RETURN (
        # Write your MySQL query statement below.
        select min(a.salary)
        from employee a 
        where (
            select count(*)+1 
            from  (select distinct salary from employee) b 
            where b.salary>a.salary
        )=N
    );
END

4. 解法四:使用子查詢&笛卡爾積

  • 排名第N的薪水意味著該表中存在N-1個(gè)比其更高的薪水
  • 注意這里的N-1個(gè)更高的薪水是指去重后的N-1個(gè),實(shí)際對(duì)應(yīng)人數(shù)可能不止N-1個(gè)
  • 最后返回的薪水也應(yīng)該去重,因?yàn)榭赡懿恢挂粋€(gè)薪水排名第N
  • 由于對(duì)于每個(gè)薪水的where條件都要執(zhí)行一遍子查詢,注定其效率低下
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    RETURN (
        # Write your MySQL query statement below.
        SELECT DISTINCT e.salary
        FROM employee e
        WHERE (
            SELECT count(DISTINCT salary) 
            FROM employee 
            WHERE salary>e.salary
        ) = N-1
    );
END

當(dāng)然,可以很容易將上面的代碼改為笛卡爾積連接形式,其執(zhí)行過程實(shí)際上一致的,甚至MySQL執(zhí)行時(shí)可能會(huì)優(yōu)化成相同的查詢語句。

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    RETURN (
        # Write your MySQL query statement below.
        SELECT e1.salary
        FROM employee e1, employee e2 
        WHERE e1.salary <= e2.salary
        GROUP BY e1.salary
        HAVING count(DISTINCT e2.salary) = N
    );
END

5. 解法五:使用自連接

一般來說,能用子查詢解決的問題也能用連接解決。具體到本題:

  • 兩表自連接,連接條件設(shè)定為表1的salary小于表2的salary
  • 以表1的salary分組,統(tǒng)計(jì)表1中每個(gè)salary分組后對(duì)應(yīng)表2中salary唯一值個(gè)數(shù),即去重
  • 限定步驟2中having 計(jì)數(shù)個(gè)數(shù)為N-1,即實(shí)現(xiàn)了該分組中表1salary排名為第N個(gè)
  • 考慮N=1的特殊情形(特殊是因?yàn)镹-1=0,計(jì)數(shù)要求為0),此時(shí)不存在滿足條件的記錄數(shù),但仍需返回結(jié)果,所以連接用left join
  • 如果僅查詢薪水這一項(xiàng)值,那么不用left join當(dāng)然也是可以的,只需把連接條件放寬至小于等于、同時(shí)查詢個(gè)數(shù)設(shè)置為N即可。因?yàn)檫B接條件含等號(hào),所以一定不為空,用join即可。
  • 注:個(gè)人認(rèn)為無需考慮N<=0的情形,畢竟無實(shí)際意義。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    RETURN (
        # Write your MySQL query statement below.
        SELECT e1.salary
        FROM  employee e1 JOIN employee e2 
        ON e1.salary <= e2.salary
        GROUP BY e1.salary
        HAVING count(DISTINCT e2.salary) = N
    );
END

但需要注意的是在題目測(cè)試時(shí)候,這種解法戶報(bào)錯(cuò),因?yàn)槭亲赃B接兩個(gè)數(shù)比較。如果求第一個(gè)數(shù),其中有一個(gè)必為空,會(huì)出錯(cuò) ~

6. 解法六:使用窗口函數(shù)

窗口函數(shù)用法具體見2.5

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
        SELECT DISTINCT salary
        FROM (
            SELECT salary, dense_rank() over(ORDER BY salary DESC) AS rnk
            FROM employee
        ) tmp
        WHERE rnk = N
  );
END

四、總結(jié)

至此,可以總結(jié)MySQL查詢的一般性思路是:

  • 能用單表優(yōu)先用單表,即便是需要用group by、order by、limit等,效率一般也比多表高
  • 不能用單表時(shí)優(yōu)先用連接,連接是SQL中非常強(qiáng)大的用法,小表驅(qū)動(dòng)大表+建立合適索引+合理運(yùn)用連接條件,基本上連接可以解決絕大部分問題。但join級(jí)數(shù)不宜過多,畢竟是一個(gè)接近指數(shù)級(jí)增長(zhǎng)的關(guān)聯(lián)效果
  • 能不用子查詢、笛卡爾積盡量不用,雖然很多情況下MySQL優(yōu)化器會(huì)將其優(yōu)化成連接方式的執(zhí)行過程,但效率仍然難以保證
  • 如果MySQL版本允許,某些帶聚合功能的查詢需求應(yīng)用窗口函數(shù)是一個(gè)最優(yōu)選擇。除了經(jīng)典的獲取3種排名信息,還有聚合函數(shù)、向前向后取值、百分位等,具體可參考官方指南。以下是官方給出的幾個(gè)窗口函數(shù)的介紹:

五、參考鏈接

  • leetcode-cn.com/problems/nt…
  • leetcode-cn.com/problems/nt…
  • leetcode-cn.com/problems/nt…
  • leetcode-cn.com/problems/nt…
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容