Leetcode SQL(五)

目錄

177. 第N高的薪水

618. 學生地理信息報告🔒


 

177. 第N高的薪水

https://leetcode-cn.com/problems/nth-highest-salary/

題解

一:dense_rank函數。

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 cnt 
      from Employee)a where cnt = N
  );
END

二:這邊mark一下一位大神的題解,轉自https://leetcode-cn.com/problems/nth-highest-salary/solution/mysql-zi-ding-yi-bian-liang-by-luanz/

排名是數據庫中的一個經典題目,實際上又根據排名的具體細節可分爲3種場景:

  1. 連續排名,例如薪水3000、2000、2000、1000排名結果爲1-2-3-4,體現同薪不同名,排名類似於編號
  2. 同薪同名但總排名不連續,例如同樣的薪水分佈,排名結果爲1-2-2-4
  3. 同薪同名且總排名連續,同樣的薪水排名結果爲1-2-2-3

不同的應用場景可能需要不同的排名結果,也意味着不同的查詢策略。本題的目標是實現第三種排名方式下的第N個結果,且是全局排名,不存在分組的問題,實際上還要相對簡單一些。

值得一提的是:在Oracle等數據庫中有窗口函數,可非常容易實現這些需求,而MySQL直到8.0版本也引入相關函數。當前MySQL OJ系統爲5.7版本(可通過編碼區——語言選擇按鈕——左側的"i"查看環境信息),所以不能直接應用。

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

  1. 能用單表優先用單表,即便是需要用group by、order by、limit等,效率一般也比多表高
  2. 不能用單表時優先用連接,連接是SQL中非常強大的用法,小表驅動大表+建立合適索引+合理運用連接條件,基本上連接可以解決絕大部分問題。但join級數不宜過多,畢竟是一個接近指數級增長的關聯效果
  3. 能不用子查詢、笛卡爾積儘量不用,雖然很多情況下MySQL優化器會將其優化成連接方式的執行過程,但效率仍然難以保證
  4. 自定義變量在複雜SQL實現中會很有用,例如LeetCode中困難級別的數據庫題目很多都需要藉助自定義變量實現
  5. 如果MySQL版本允許,某些帶聚合功能的查詢需求應用窗口函數是一個最優選擇。除了經典的獲取3種排名信息,還有聚合函數、向前向後取值、百分位等,具體可參考官方指南。以下是官方給出的幾個窗口函數的介紹:

image.png

最後的最後再補充一點,本題將查詢語句封裝成一個自定義函數並給出了模板,實際上是降低了對函數語法的書寫要求和難度,而且提供的函數寫法也較爲精簡。然而,自定義函數更一般化和常用的寫法應該是分三步:

  1. 定義變量接收返回值
  2. 執行查詢條件,並賦值給相應變量
  3. 返回結果

由於本題不存在分組排序,只需返回全局第N高的一個,所以自然想到的想法是用order by排序加limit限制得到。需要注意兩個細節:

  1. 同薪同名且不跳級的問題,解決辦法是用group by按薪水分組後再order by
  2. 排名第N高意味着要跳過N-1個薪水,由於無法直接用limit N-1,所以需先在函數開頭處理N爲N=N-1。

注:這裏不能直接用limit N-1是因爲limit和offset字段後面只接受正整數(意味着0、負數、小數都不行)或者單一變量(意味着不能用表達式),也就是說想取一條,limit 2-1、limit 1.1這類的寫法都是報錯的。
注:這種解法形式最爲簡潔直觀,但僅適用於查詢全局排名問題,如果要求各分組的每個第N名,則該方法不適用;而且也不能處理存在重複值的情況。

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

618. 學生地理信息報告🔒

https://leetcode-cn.com/problems/students-report-by-geography/

一所美國大學有來自亞洲、歐洲和美洲的學生,他們的地理信息存放在如下 student 表中。

寫一個查詢語句實現對大洲(continent)列的 透視表 操作,使得每個學生按照姓名的字母順序依次排列在對應的大洲下面。輸出的標題應依次爲美洲(America)、亞洲(Asia)和歐洲(Europe)。數據保證來自美洲的學生不少於來自亞洲或者歐洲的學生。對於樣例輸入,它的對應輸出是:

進階:如果不能確定哪個大洲的學生數最多,你可以寫出一個查詢去生成上述學生報告嗎?

題解:

一:不考慮進階版,即默認美洲學生數最多。轉自https://leetcode-cn.com/problems/students-report-by-geography/solution/xue-sheng-di-li-xin-xi-bao-gao-by-leetcode/, 使用 "session 變量" 和 join。思路:爲每個大洲分配一個單獨的行自增 id,然後將它們連接。算法:使用 session 變量爲每個大洲分配單獨的行自增 id。例如下面語句爲美洲的學生分配行自增 id。

SELECT row_id, America FROM (SELECT @am:=0) t,
(SELECT @am:=@am + 1 AS row_id, name AS America FROM student 
WHERE continent = 'America' ORDER BY America) AS t2

select America, Asia, Europe from
(select @am_id:=0, @as_id:=0, @eu_id := 0)t,
(select @am_id := @am_id + 1 as am_id, 
case when continent = 'America' then name else null end America
from student where continent = 'America' order by name)t1
left join 
(select @as_id := @as_id + 1 as as_id, 
case when continent = 'Asia' then name else null end Asia
from student where continent = 'Asia' order by name)t2 on am_id = as_id
left join 
(select @eu_id := @eu_id + 1 as eu_id, 
case when continent = 'Europe' then name else null end Europe
from student where continent = 'Europe' order by name)t3 on am_id = eu_id

用row_number函數產出id

from student where continent = 'America' order by name)t1
left join 
(select row_number() over() as as_id, 
case when continent = 'Asia' then name else null end Asia
from student where continent = 'Asia' order by name)t2 on am_id = as_id
left join 
(select row_number() over() as eu_id, 
case when continent = 'Europe' then name else null end Europe
from student where continent = 'Europe' order by name)t3 on am_id = eu_id

二:轉自https://leetcode-cn.com/problems/students-report-by-geography/solution/liang-chong-jie-fa-zheng-he-by-cherry-42/, 進階的解法:使用case when ...(參考其他大神的解法)。思路:同樣先是根據洲名分組,對同一洲名的學生進行組內排序編號;然後分別判斷這些學生的所屬洲:如果是美洲,則將該學生的名字賦值給美洲,否則置null;如果是亞洲,則將該學生的名字賦值給亞洲,否則置null;如果是歐洲,則將該學生的名字賦值給歐洲,否則置null;到這一步,就已經實現了列轉行。下面需要將列轉行的結果根據編號整合一下。最後將這些記錄根據排序的編號分組,分別取美洲、亞洲、歐洲欄位的最大值,這樣就可以將同一編號的學生放在一起。同一編號沒有學生的洲就爲NULL。

select name, continent
,case when continent='America' then name else null end as America
,case when continent='Asia' then name else null end as Asia
,case when continent='Europe' then name else null end as Europe
from student

select row_number() over(partition by continent order by name) as cnt
,name, continent from student

select id, case when continent="America" then name else null end America,
case when continent="Asia" then name else null end Asia,
case when continent="Europe" then name else null end Europe
from
(select row_number() over(partition by continent order by name) as id, name, continent
from student)t

select id, max(case when continent="America" then name else null end) America,
max(case when continent="Asia" then name else null end) Asia,
max(case when continent="Europe" then name else null end) Europe
from
(select row_number() over(partition by continent order by name) as id, name, continent
from student)t group by id

select max(case when continent="America" then name else null end) America,
max(case when continent="Asia" then name else null end) Asia,
max(case when continent="Europe" then name else null end) Europe
from
(select row_number() over(partition by continent order by name) as id, name, continent
from student)t group by id

569. 員工薪水中位數🔒

https://leetcode-cn.com/problems/median-employee-salary/

請編寫SQL查詢來查找每個公司的薪水中位數。挑戰點:你是否可以在不使用任何內置的SQL函數的情況下解決此問題。

題解

一:借這一題來求解每個公司的工資最高的額三個人。用row_number函數分組排序,由於工資同的人員的rn一樣,故這邊每個公司返回的人數可能不止三個人。

select Company, Salary, rn from 
(select dense_rank() over(partition by Company order by Salary desc) as rn,
Company, Salary from Employee)t where rn <= 3 order by Company, Salary desc

二:方便的窗口函數,

select id, row_number() over(partition by Company order by Salary, id) as rn,
count(1) over(partition by Company) as cnt,
Company, Salary from Employee

select id, Company, Salary from
(select id, row_number() over(partition by Company order by Salary, id) as rn,
count(1) over(partition by Company) as cnt,
Company, Salary from Employee)t 
where (mod(cnt, 2)=1 and rn = round(cnt / 2,0))
or (mod(cnt, 2)=0 and (rn = round(cnt / 2,0) or rn = round(cnt / 2,0) + 1))
order by Company, Salary 

262. 行程和用戶

https://leetcode-cn.com/problems/trips-and-users/

Trips 表中存所有出租車的行程信息。每段行程有唯一鍵 Id,Client_Id 和 Driver_Id 是 Users 表中 Users_Id 的外鍵。Status 是枚舉類型,枚舉成員爲 (‘completed’, ‘cancelled_by_driver’, ‘cancelled_by_client’)。

Users 表存所有用戶。每個用戶有唯一鍵 Users_Id。Banned 表示這個用戶是否被禁止,Role 則是一個表示(‘client’, ‘driver’, ‘partner’)的枚舉類型。

寫一段 SQL 語句查出 2013年10月1日 至 2013年10月3日 期間非禁止用戶的取消率。基於上表,你的 SQL 語句應返回如下結果,取消率(Cancellation Rate)保留兩位小數。取消率的計算方式如下:(被司機或乘客取消的非禁止用戶生成的訂單數量) / (非禁止用戶生成的訂單總數)

題解

一:無摘要

select Request_at Day, 
round(ifnull(count(Status="cancelled_by_driver" or Status="cancelled_by_client" or null) /
 count(Status), 0), 2) as 'Cancellation Rate' from 
(select Status, Request_at from Trips 
left join Users a on a.Users_Id = Client_Id
left join Users b on b.Users_Id = Driver_Id
where a.Banned = 'No' and b.Banned = 'No'
and  Request_at >= "2013-10-01" and Request_at <= "2013-10-03")c
group by Day order by Day

1205. 每月交易II🔒

https://leetcode-cn.com/problems/monthly-transactions-ii/

Transactions 記錄表

id 是這個表的主鍵。該表包含有關傳入事務的信息。狀態列是類型爲 [approved(已批准)、declined(已拒絕)] 的枚舉。
Chargebacks 表

退單包含有關放置在事務表中的某些事務的傳入退單的基本信息。trans_id 是 transactions 表的 id 列的外鍵。每項退單都對應於之前進行的交易,即使未經批准。
編寫一個 SQL 查詢,以查找每個月和每個國家/地區的已批准交易的數量及其總金額、退單的數量及其總金額。注意:在您的查詢中,給定月份和國家,忽略所有爲零的行。查詢結果格式如下所示:

題解

一:轉自https://leetcode-cn.com/problems/monthly-transactions-ii/solution/zhe-dao-ti-mei-sha-yi-si-ti-gong-yi-ge-jian-dan-de/,UNION,然後給UNION過來的記錄打個標籤用來區分就行了。

SELECT
  DATE_FORMAT(trans_date, '%Y-%m') `month`,
  country,
  COUNT(IF(state = 'approved', 1, NULL)) approved_count,
  SUM(IF(state = 'approved', amount, 0)) approved_amount,
  COUNT(IF(state = 'chargeback', 1, NULL)) chargeback_count,
  SUM(IF(state = 'chargeback', amount, 0)) chargeback_amount
FROM
(
  SELECT
    trans_date, id, country, state, amount
  FROM
    Transactions
  UNION 
  SELECT 
    c.trans_date, c.trans_id, t.country, 'chargeback' state, t.amount
  FROM
    Chargebacks c
    LEFT JOIN Transactions t ON c.trans_id = t.id
) t
GROUP BY
  1,2
HAVING 
  approved_count + chargeback_count > 0

 

 

 

 

 

 

 

 

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