Mysql排名問題

簡述

最近在刷題和工做中總會遇到前n高,第n高的問題,彙總一下以便往後查看。
排名3種場景(以薪水爲例):mysql

  • 連續排名,同薪不一樣名。3000、2000、2000、1000的排名爲1-2-3-4
  • 不連續排名,同薪同名。3000、2000、2000、1000的排名爲1-2-2-4
  • 連續排名,同薪同名。3000、2000、2000、1000的排名爲1-2-2-3
    下面的兩個例子都以連續排名,同薪同名的狀況舉例

第N高薪水(連續排名,同薪同名)


如表中所示,若是存在第N高的薪水則返回Salary,若是不存在那麼查詢應該返回NULLsql

單表查詢

  • 解題思路
    全局排名,不分組,因此咱們能夠用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行

  1. 代碼片斷
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

子查詢方式1

  1. 解題思路
    先查出前n高的薪水,再從中查詢最低的薪水(即第n高的薪水),並用COUNT(1)累加用來判斷是否有第n高的薪水 。考慮會有相等的薪水因此第一重查詢用DISTINCT去重。
  2. 代碼片斷
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

子查詢方式2

  1. 解題思路
    排名第N高意味着表中存在N-1個比其更高的薪水(去重前提下)。
    聯表查詢出比當前薪水高的有幾個,若是這個數量等於N-1,那麼返回該薪水。
  2. 代碼片斷
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

自鏈接

  1. 解題思路
    自鏈接條件爲表1的Salary小於表2的Salary,以表1的Salary分組,統計表2的Salary的去重個數
    考慮到第一名的表2的Salary爲空,因此採用LEFT JOIN ,當去重個數等於N-1時就是要輸出的排名(也能夠用JOIN,鏈接條件爲<=COUNT(DISTINCT e2.Salary) = N)
  2. 代碼片斷
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

笛卡爾積

  1. 解題思路
    跟子查詢方式2類似,再也不贅述。
  2. 代碼片斷
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

自定義變量

  1. 解題思路
    自定義兩個變量,@s存儲工資,@r存儲排名,先按工資排序,查詢時更新變量值,當工資相等時排名不變,不相等則排名加一
  2. 代碼片斷
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

開窗函數

  1. 解題思路
    mysql8.0以上版本能夠用開窗函數,效率是最好的,經常使用的三種排名函數以下:
  • ROW_NUMBER():連續排名,同薪不一樣名,3000、2000、2000、1000的排名爲1-2-3-4
  • RANK(): 不連續排名,同薪同名。3000、2000、2000、1000的排名爲1-2-2-4
  • DESENSE_RANK():連續排名,同薪同名。3000、2000、2000、1000的排名爲1-2-2-3
    這三個函數要和OVER()一塊兒使用,OVER()中的參數一般是PARTITION BYORDER BY 。例題狀況是第三種,因此採用DENSE_RANK()
  1. 代碼示例
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高薪水(連續排名,同薪同名)

在這裏插入圖片描述
在這裏插入圖片描述
如表中所示,若是存在部門前N高的薪水則返回DepartmentId + Salary,若是不存在那麼查詢應該返回NULL。由於只考慮部門和薪水,因此仍是連續排名,同薪同名。
3d

子查詢方式

  1. 解題思路(和第N高薪水的子查詢方式2相似)
    工資前N高意味着:有不超過N-1我的的工資比查詢結果的工資高。例如求前三高的工資,即有不超過2我的(查詢子條件爲<=2或<3)的工資比查詢結果的工資高(有0我的比第一高工資高;有1我的比第二高工資高;有2我的比第三高工資高)
  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
;

鏈接查詢

  1. 解題思路
    能用子查詢解決的問題通常都能用鏈接來解決
  2. 代碼示例
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

自定義變量

  1. 解題思路
    自定義三個變量,@s存儲工資,@r存儲排名,@d存儲部門ID,先按部門和工資排序,查詢時更新變量值。
    (1)當前部門ID與@d相同(@d=DepartmentId),則表明是在同一部門中進行的排名,當工資相等(@s=Salary)時排名不變(@r:=@r),不相等則排名加一(@r:=@r+1);
    (2)當前部門ID與@d不相同(@d!=DepartmentId),則說明@d需從新賦值(@d=DepartmentId),排名也要從新開始,即@r:=1
  2. 代碼示例
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

開窗函數

  1. 解題思路
    又到了快樂的開窗函數,由於是同薪同名,連續排名,因此仍是用DENSE_RANK(),由於求的是部門前N高薪水,因此按部門分組再按薪水排序,那麼開窗函數的使用就是:DENSE_RANK() OVER(PARTITION BY departmentid ORDER BY salary DESC)
  2. 代碼示例
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
	;
相關文章
相關標籤/搜索