數據庫 select over 方法

  OVER子句用於爲行爲定義一個窗口(windows),以便進行特定的運算。可以把行的窗口簡單地認爲是運算將要操作的一個行的集合。例如,聚合函數和排名函數都是可以支持OVER子句的運算類型。由於OVER子句爲這些函數提供了一個行的窗口,所以這些函數也稱之爲開窗函數。

  聚合函數的要點就是要對一組值進行聚合,聚合函數傳統上一直以GROUP BY查詢作爲操作的上下文。在前面的“GROUP BY”子句的討論中,我們知道在對數據進行分組以後,查詢爲每個組只返回一行;因此,也就是要限制所有的表達式爲每個組只能返回一個值。

  聚合開窗函數使用OVER子句提供窗口作爲上下文,對窗口中的一組值進行操作,而不是使用GROUP BY子句提供的上下文。這樣就不必對數據進行分組,還能夠在同一行中同時返回基礎行的列和聚合列。爲了理解OVER子句,現在考慮Sales.OrderValues視圖,現在只要簡單地把視圖看作是一個表就可以了。Sales.OrderValues視圖的每一行代表一個訂單,包含訂單ID(orderid),客戶ID(custid)、僱員ID(empid)、承運人ID(shipperid)、訂單日期(orderdate),以及訂單價格(val)。

  帶有空括號的OVER子句會提供所有行進行計算。這裏的“所有行”並不一定是在FROM子句中出現的那些表中的所有行,而是在FROM、WHERE、GROUP BY,以及HAVING處理階段完成後仍然可用的那些行。注意,只有在SELECT和ORDER BY處理階段才允許使用OVER子句。這裏只中點介紹在SELECT處理階段如何使用OVER子句。例如,如果在對OrderValues視圖進行查詢的SELECT子句中指定了SUM(val) OVER()表達式,這個函數就會對SELECT階段操作的所有行計算其總價格。

  如果查詢在SELECT階段之前沒有過濾數據,也沒有應用其他的邏輯階段,這個表達式將返回OrderValues視圖中所有行的總價格。如果想對行進行限制或分區,則可以使用PARTITION BY子句。例如,現在不想返回OrderValues中所有行的總價格,而只是想返回當前客戶(和當前行具有相同custid的所有行)的總價格,則可以指定SUM(val) OVER(PARTITION BY custid)。

  以下查詢可以返回OrderValues的所有行,並且演示了同時使用非分區和分區表達式的方法。此外,每一行除了基本列以外,查詢還會返回所有行的總價格和當前客戶的總價格:

1SELECT orderid,custid,val,
2SUM(val) OVER() AS totalvalue,
3SUM(val) OVER(PARTITION BY custid) AS custtotalvalue
4FROM Sales.OrderValues;

  這個查詢返回以下結果:

  所有結果行的totalvalue列表示所有行的價格總數。custtotalvalue列表示所有行中與當前行具有相同custid值的那些行的價格總數。

  OVER子句的一個優點就是能夠在返回基本列的同時,在同一行對它們進行聚合;也可以在表達式中混合使用基本列和聚合值列。例如,以下查詢爲OrderValues的每一行計算當前價格佔總價格的百分比,以及當前價格佔客戶總價格的百分比。

1SELECT orderid,custid,val,
2100* val /SUM(val) OVER() AS totalvalue,
3100* val /SUM(val) OVER(PARTITION BY custid) AS custtotalvalue
4FROM Sales.OrderValues;

  注意,在表達式中使用的是十進制實數100.(100後面加個點),而不是直接使用整數100,因爲這樣可以隱式地將整數數值val和SUM(val)轉換成十進制實數值。否則,表達式中的除法將是“整數除法”,會截去數值的小數部分。

  這個查詢返回以下結果:

  OVER子句也支持四種排名函數:ROW_NUMBER(行號)、RANK(排名)、DENSE_RANK(密集排名),以及NTILE。以下查詢演示了這些函數的用法:

1SELECT orderid,custid,val,
2 ROW_NUMBER() OVER(ORDERBY val) AS rownum,
3 RANK() OVER(ORDERBY val) AS rank,
4 DENSE_RANK() OVER(ORDERBY val) AS dense_rank,
5 NTILE(10OVER(ORDERBY val) AS ntile
6FROM Sales.OrderValues
7ORDERBY val;

  該查詢會生成以下輸出:

  ROW_NUMBER函數用於爲查詢的結果集中的各行分配遞增的序列號,其邏輯順序通過OVER子句中的ORDER BY語句進行指定。在我們的查詢例子中,邏輯順序基於的是val列;因此,從輸出中可以看到,隨着訂單價格的增加,行號也隨之增加。不過,即使訂單價格沒有增加,行號也會依然增加。所以,如果ROW_NUMBER函數的ORDER BY不能唯一確定行的順序(如前面這個例子所示),查詢結果就是不確定的。也就是說,查詢可能返回多個正確的結果。例如,可以看到訂單價格爲36.00的兩行,它們的行號分別是7和8。如果這些行的順序發生了變化,結果都可以認爲是正確的。如果想讓行號計算值是確定的,則必須在ORDER BY列表中添加元素,讓它具有唯一性;也就是說,要讓ORDER BY子句中列出的元素能夠唯一地確定各個行。例如,可以在ORDER BY列表中增加orderid作爲附加屬性,這樣,行號計算結果就成爲確定的。

  前面提過,即使行的排序值相同,ROW_NUMBER函數也一定爲其生成唯一的行號值。如果想以同樣的方式對排序值中的相同值進行更多的處理,可以考慮使用RANK或DENSE_RANK函數。這兩個函數與ROW_NUMBER類似,但它們爲具有相同邏輯排序值的所有行生成同樣的排名。RANK和DENSE_RANK的區別是:RANK表示之前有多少行具有更低的排序值,而DENSE_RANK則表示之前有多少個更低的排序值。例如,在我們的查詢例子中,rank列爲9表示前面有8行具有更小的排序值,dense_rank列爲9表示前面有8個更小的不同排序值。

  NTILE函數可以把結果中的行關聯到組(tile,相當於由行組成的指定數目的組),併爲每一行分配一個所屬的組的編號。NTILE函數接受一個表示組的數量的輸入參數,並要在OVER子句中指定邏輯順序。我們的查詢例子有830行,請求將其分成10組;因此,組的大小就是83(830除以10)。邏輯排序基於的是val列,這意味着價格最低的83行將分在第1組,接下來的83行將分在第2組,在接下來的83行將分在第3組,以此類推。NTILE函數在邏輯上需要依賴於ROW_NUMBER函數。整個過程是先根據對val的排序結果,爲每一行分配行號;在基於前面計算好的組的大小83行,將第1行到第83行分配到第1組,將第84行到第166行分配到第2組,以此類推。如果組數無法整除表的行數,餘數(remainder)中的每一行會被分配到最前面的每個組。例如,假設有102行,請求分成5組,那麼前兩組將有21行而不是20行。

  和聚合開窗函數類似,排名函數也支持在OVER子句中使用PARTITION BY語句。以排名計算作爲背景,理解PARTITION BY子句的含義可能要更容易些。例如,與在整個集合中分配行號不同,表達式ROW_NUMBER() OVER(PARTITION BY custid ORDER BY val)爲各行中具有相同custid的子集獨立地分配行號。在以下查詢中使用了這個表達式:

1SELECT orderid,custid,val,
2 ROW_NUMBER() OVER(PARTITION BY custid ORDERBY val) AS rownum
3FROM Sales.OrderValues
4ORDERBY custid,val;

  該查詢會生成以下輸出:

  從這些輸出中可以看到,行號是爲每個客戶獨立計算的,好像計算過程爲每個客戶重置了一樣。

  注意,OVER子句中指定的ORDER BY邏輯與數據展示沒什麼關係,並不會改變查詢結果表最終的任何內容。如果在查詢中不指定ORDER BY,和前面介紹的一樣,就不能保證輸出中行的任何順序。如果需要確保查詢結果的排序順序,就必須在ORDER BY子句中增加相應的排序條件,就像前面排名函數的最後兩個查詢例子演示的那樣。

  如果在SELECT處理階段指定了開窗函數,開窗計算會在DISTINCT子句(如果存在)之前進行處理。

  作爲對目前爲止已經討論過的所有子句的總結,以下列出了它們的邏輯處理順序:

FROMWHEREGROUP BYHAVINGSELECT  OVERDISTINCETOPORDER BY

  你考慮過DISTINCT子句爲什麼要在SELECT中的開窗計算之後進行處理,不能在它前面處理嗎?這裏用一個例子來說明這個問題。OrderValues視圖目前有830行,795個不同的值。考慮以下查詢極其輸出:

1SELECTDISTINCT val,ROW_NUMBER() OVER(ORDERBY val) AS rownum
2FROM Sales.OrderValues;

  輸出結果如下所示:

  ROW_NUMBER函數是在DISTINCT子句之前處理的。首先,爲OrderValues視圖中的830行分配唯一的行號。接着再處理DISTINCT子句,所以這時不會有任何重複的行要刪除。可以認爲在同一SELECT子句中不能同時指定DISTINCT和ROW_NUMBER是一個最佳實踐原則,因爲DISTINCT子句在這種情況下不起任何作用。如果想要爲795個唯一值分配行號,那就需要採用其他不同的解決辦法。例如,因爲GROUP BY階段是在SELECT階段之前處理的,可以使用以下查詢:

1SELECT val,ROW_NUMBER() OVER(ORDERBY val) AS rownum
2FROM Sales.OrderValues
3GROUPBY val;

  這裏,GROUP BY處理階段爲795個不重複的值生成了795個組,接着SELECT處理階段爲每個組中的價格值和行號(基於對val的排序)生成一行。

發佈了0 篇原創文章 · 獲贊 4 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章