高級查詢

  本篇文章示例介紹的查詢操作不同於其他查詢操作,它們體現了不同的查詢思路,需要以每次一頁的方式顯示結果集。

 

1.在結果集中翻頁

  問題:返回員工表中薪水排名前五的員工,然後再返回接下來排名前五的員工。

  解決方案:這個問題看起來很簡單,只需要根據「薪水」這一列進行倒序排序,然後分頁操作即可。但是如果存在重複數據時就會有問題。

  先按照「薪水」這一列進行倒序排序:

  

 

  然後分頁取排名前五的數據:

  

 

 

   看似沒問題,但是題目要求返回的是薪水排名前五的數據。因為這裡有薪水相同的數據,返回的數據其實不止五條。應該返回下面框起來的數據:

  

 

 

   遇到這種情況,需要使用窗函數 rank() over 給每條數據進行排名(當前行在其分區內的排名,有間隙。要無間隙可以使用 dense_rank 。不使用ROW_NUMBER() ,該函數不區分使用並列),然後使用 between and 語法分頁。

select * from (
SELECT *,rank() over(order by SAL desc) as rn FROM test.emps ) a where rn between 1 and 5

  

 

 

 

2.在表中跳過 n 行數據

  問題:以每次跳過一人的方式返回員工表中的數據。換言之,想返回第一個員工,第三個員工 ......

  解決方案:先對結果集排序,否則不存在「第一個」,「第二個」,「下一個」的概念。然後使用窗函數 ROW_NUMBER() 給每一行進行編號,再結合這些編號和求模函數 mod() 跳過不想返回的行。

select *,mod(rn,2) from (
select *,row_number() over(order by id) as rn from emps ) a
where mod(rn,2) = 1

  

 

 

 

3.確定哪些行是互逆的

  問題:有一張包含兩次考試結果的表,想確定哪兩組成績是互逆的。

  

 

 

   從這些數據可知,Test1 為70、Test2 為90與Test1 為90、Test2 為70 是互逆的。Test1 為20、Test2 為20與Test1 為20、Test2 為20 是互逆的。希望返回如下結果:

  

 

 

   解決方案:使用自連接找出這樣的行,即一行的 Test1 和Test2 與 另一行的 Test2和Test1 相等。然後去重。

SELECT a.test1,a.test2 FROM test.grades a
join test.grades b on a.test1=b.test2 and a.test2 = b.test1
where a.id < b.id

  這裡使用 a.id < b.id 去重。

 

4.平移行值

  問題:返回每位員工的姓名,薪水,下一個更高和更低的薪水值。如果沒有更高或更低的薪水值,就執行迴轉操作。即如果當前員工的薪水是最低的,則將它的下一個更低的薪水值設置為最高的薪水;如果當前員工的薪水是最高的,就將它的下一個更高的薪水設置為最低的薪水。

  解決方案:使用窗函數 Lead over 和 Lag over 可以輕鬆取到前一行和後一行數據。然後使用 min 和 max 獲取最小和最大值。

  第一步:取下一行數據的薪水作為當前行的下一個更高的薪水,取上一行的薪水作為下一個更低薪水

select ename,sal,lead(sal) over(order by sal) 下一個更高的薪水,lag(sal) over(order by sal) 下一個更低薪水 from emps

  

 

 

   第二步:處理第一行和最後一行

select ename,sal,
coalesce( lead(sal) over(order by sal),min(sal) over()) 下一個更高的薪水,
coalesce( lag(sal) over(order by sal),max(sal) over()) 下一個更低薪水 from emps

  

 

 

 

5.消除重複行

  問題:找出員工表中不同的部門編號,但不想看到重複的行。

  解決方案:最簡單的方法是使用 distinct 關鍵字或者使用 group by 。另一種解決方法是,使用窗函數 row_number over 。

select * from (
select DeptNo,row_number() over(partition by  DeptNo order by deptno) as rn  from emps ) a
where rn = 1

  row_number() over(partition by  DeptNo order by deptno) as rn 根據部門編號分區,給分區內每一行進行編號。然後取分區內第一行。

  

 

 

 

6.查找馬值

  問題:返回一個結果集,包含員工名字、部門編號、薪水、獲聘日期以及所屬部門最後聘請的員工的薪水。

    所屬部門最後聘請的員工的薪水就是馬值,因為查找這些值的方法與國際象棋中馬的走法類似。先跳到某一行,然後跳到某一列。同樣,要找到所屬部門最後聘請的員工的薪水,必須先定位到最後獲聘日期所在的行,然後獲取該行的薪水值。

  解決方案:使用  max(HireDate) over(partition by DeptNo)  和 case when 語法找出所屬部門最後聘請的員工。再次使用 Max Over 獲取所屬部門最後聘請的員工的薪水值。

select DeptNo,Ename,Sal,HireDate, max(Lastest_Sal) over(partition by DeptNo) from (
select DeptNo,Ename,Sal,HireDate,
case  max(HireDate) over(partition by DeptNo) 
    when HireDate then SAL else 0 
    end  as Lastest_Sal
    from emps ) a

 

7.生成簡單預測

  問題:根據當前的數據返回表示未來行動的行和列。例如,下面有三行訂單數據:

  

 

   每行數據包含下單日期和處理日期(下單兩天後)。現在想對每行數據都返回三行(當前行和另外兩行),還想返回另外兩列,訂單核驗日期(訂單處理一天後)和發貨日期(訂單核驗一天後)。並且想返回一個呈現整個過程的結果集,下圖的樣子:

  

 

   

  解決方案:使用笛卡爾積為每行訂單再生成兩行樹,然後使用 Case 表達式來創建所需的列值。

with recursive t3 (n) as
(
    select 1 as n
    union all
    select n+1 from t3 where n< 3
)


select id, OrderDate 下單日期,processDate 處理日期, 
case when n >= 2 then adddate(OrderDate,interval 1 day)
    else '' end as 核驗日期,
case when n>2 then adddate(OrderDate,interval 2 day)
    else '' end as 發貨日期
 from orders o
join t3

 

Tags: