最近在刷題和工做中總會遇到前n高,第n高的問題,彙總一下以便往後查看。
排名3種場景(以薪水爲例):mysql
如表中所示,若是存在第N高的薪水則返回Salary
,若是不存在那麼查詢應該返回NULL
。sql
ORDER BY
排序加LIMIT N,M
限制(M表示在限制條數以後的offset記錄,LIMIT M OFFSET N
),排名第N高意思是LIMIT N-1,1
,可是LIMIT
後面只接受正整數或者單一變量,不能用表達式,因此在函數中須要先SET N = N - 1
GROUP BY
按薪水分組後再ORDER BY
或者DISTINCT
去重。MySQL中的LIMIT用法詳解函數
- 基本語法:
SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
- LIMIT子句用於select中,對輸出結果集的行數進行約束,LIMIT接受一個或兩個數字參數。參數必須是一個整數常量。offset表示偏移量(指向數據記錄的遊標),rows表示查詢限定返回的最大記錄數。當offset參數省略時,默認爲0,即LIMIT 3 等同於LIMIT 0,3。
SELECT * FROM table LIMIT 3, 4;
返回第4-7行SELECT * FROM table LIMIT 3;
返回前3行
CREATE FUNCTION getNHighestSalary(N INT) RETURNS INT BEGIN SET N := N-1; IF (N < 0) THEN RETURN NULL; ELSE RETURN ( SELECT DISTINCT salary FROM employee -- GROUP BY salary ORDER BY salary DESC LIMIT N, 1 ); END IF; END
COUNT(1)
累加用來判斷是否有第n高的薪水 。考慮會有相等的薪水因此第一重查詢用DISTINCT
去重。CREATE FUNCTION getNHighestSalary(N INT) RETURNS INT BEGIN RETURN( SELECT IF(count < N, NULL, min) AS Salary FROM ( SELECT MIN(Salary) AS min, COUNT(1) AS count FROM ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT N ) a ) b ); END
N-1
個比其更高的薪水(去重前提下)。N-1
,那麼返回該薪水。CREATE FUNCTION getNHighestSalary(N INT) RETURNS INT BEGIN RETURN( SELECT DISTINCT(e.salary) FROM Employee e WHERE ( SELECT COUNT(DISTINCT salary) FROM Employee e1 WHERE e1.salary > e.salary ) = N - 1 ); END
Salary
小於表2的Salary
,以表1的Salary
分組,統計表2的Salary
的去重個數Salary
爲空,因此採用LEFT JOIN
,當去重個數等於N-1時就是要輸出的排名(也能夠用JOIN
,鏈接條件爲<=
,COUNT(DISTINCT e2.Salary) = N)
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT BEGIN RETURN ( SELECT e1.salary FROM employee e1 LEFT JOIN employee e2 ON e1.salary < e2.salary GROUP BY e1.salary HAVING count(DISTINCT e2.salary) = N - 1 ); END
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT BEGIN RETURN ( SELECT e1.salary FROM employee e1, employee e2 WHERE e1.salary <= e2.salary GROUP BY e1.salary HAVING count(DISTINCT e2.salary) = N ); END
@s
存儲工資,@r
存儲排名,先按工資排序,查詢時更新變量值,當工資相等時排名不變,不相等則排名加一CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT BEGIN RETURN ( SELECT DISTINCT salary FROM (SELECT salary, @r:=IF(@s=salary, @r, @r+1) AS rnk, @s:= salary FROM employee, (SELECT @r:=0, @s:=NULL)init ORDER BY salary DESC) tmp WHERE rnk = N ); END
ROW_NUMBER():
連續排名,同薪不一樣名,3000、2000、2000、1000的排名爲1-2-3-4RANK():
不連續排名,同薪同名。3000、2000、2000、1000的排名爲1-2-2-4DESENSE_RANK():
連續排名,同薪同名。3000、2000、2000、1000的排名爲1-2-2-3OVER()
一塊兒使用,OVER()
中的參數一般是PARTITION BY
和 ORDER BY
。例題狀況是第三種,因此採用DENSE_RANK()
。CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT BEGIN RETURN ( SELECT DISTINCT salary FROM (SELECT salary, dense_rank() over(ORDER BY salary DESC) AS rnk FROM employee) tmp WHERE rnk = N ); END
如表中所示,若是存在部門前N高的薪水則返回DepartmentId + Salary
,若是不存在那麼查詢應該返回NULL
。由於只考慮部門和薪水,因此仍是連續排名,同薪同名。
3d
有不超過N-1我的
的工資比查詢結果的工資
高。例如求前三高的工資,即有不超過2我的
(查詢子條件爲<=2或<3)的工資比查詢結果的工資
高(有0我的
比第一高工資高;有1我的
比第二高工資高;有2我的
比第三高工資高)SELECT d.Name AS 'Department', e1.Name AS 'Employee', e1.Salary FROM Employee e1 RIGHT JOIN Department d ON e1.DepartmentId = d.Id WHERE 3 > (SELECT COUNT(DISTINCT e2.Salary) FROM Employee e2 WHERE e2.Salary > e1.Salary AND e1.DepartmentId = e2.DepartmentId ) GROUP BY e1.Salary ORDER BY d.`Name`, e1.Salary DESC ;
SELECT d.name as department, e1.name as employee, e1.salary as salary FROM Department d LEFT JOIN Employee e1 on d.id = e1.departmentid LEFT JOIN Employee e2 on e1.departmentid = e2.departmentid and e1.salary<=e2.salary GROUP BY d.name, e1.Salary HAVING count(distinct e2.salary)<4 ORDER BY d.name, e1.salary DESC
@s
存儲工資,@r
存儲排名,@d
存儲部門ID,先按部門和工資排序,查詢時更新變量值。@d
相同(@d=DepartmentId
),則表明是在同一部門中進行的排名,當工資相等(@s=Salary
)時排名不變(@r:=@r
),不相等則排名加一(@r:=@r+1
);@d
不相同(@d!=DepartmentId
),則說明@d
需從新賦值(@d=DepartmentId
),排名也要從新開始,即@r:=1
。SELECT d. NAME department, t. NAME employee, salary FROM ( SELECT *, @r :=IF (DepartmentId = @d, IF (Salary = @s, @r, @r + 1), 1) AS rnk, @d := DepartmentId, @s := Salary FROM employee, (SELECT @s := NULL,@d := NULL, @r := 0 ) init ORDER BY DepartmentId, Salary DESC ) t RIGHT JOIN department d ON t.DepartmentId = d.Id WHERE t.rnk <= N OR t.rnk IS NULL GROUP BY d.`Name`, salary ORDER BY DepartmentId, Salary DESC
DENSE_RANK()
,由於求的是部門前N高薪水,因此按部門分組再按薪水排序,那麼開窗函數的使用就是:DENSE_RANK() OVER(PARTITION BY departmentid ORDER BY salary DESC)
。SELECT d.`Name`, tmp.`Name`, tmp.Salary FROM( SELECT e1.DepartmentId, e1.`Name`, e1.Salary, DENSE_RANK() OVER(PARTITION BY e1.DepartmentId ORDER BY e1.Salary DESC) rnk FROM employee e1 ) tmp RIGHT JOIN department d ON d.Id = tmp.DepartmentId WHERE rnk <= N OR t.rnk IS NULL GROUP BY d.name, tmp.Salary ;