你真的會玩SQL嗎?系列目錄
這次講的有些可能是經常用但不會注意到,所以來統一總結一下用法。
我們往往需要臨時存儲某些結果集。除了用臨時表和表變量,還可以使用公用表表達式的方法。
表表達式
-
- 期待單個值的地方可以使用標量子查詢
- 期待多個值的地方可以使用多值子查詢
- 在期待出現表的地方可用表值子查詢或表表達式
1.派生表
是從查詢表達式派生出虛擬結果表的表表達式,派生表的存在范圍只是外部查詢。
使用形式:from 派生表 as 派生表列名
規則:
-
- 所有列必須有名稱
- 列名必須唯一
- 不允許使用order by(除非指定了top)
不同於標量和多值子查詢,派生表不能是相關的,它必須是獨立的。
2.公用表表達式(CTE)
非遞歸公用表表達式(CTE)是查詢結果僅僅一次性返回一個結果集用於外部查詢調用。
WITH CTE_Test AS ( SELECT * FROM Person_1 ) SELECT * FROM CTE_Test AS a --第一次引用 INNER JOIN CTE_Test AS b --第二次引用 ON a.Id = b.Id ORDER BY a.Id DESC
--SELECT * FROM CTE_Test 再查詢一次會報錯
遞歸公用表達式
來引用他人的一個示例:
先建一張表欄目表如下,欄目Id,欄目名稱,欄目的父欄目
現在使用CTE查詢其每個欄目是第幾層欄目的代碼如下:
WITH COL_CTE(Id,Name,ParentId,tLevel ) AS ( --基本語句 SELECT Id,Name,ParentId,0 AS tLevel FROM Col WHERE ParentId = 0 UNION ALL --遞歸語句 SELECT c.Id,c.Name,c.ParentId,ce.tLevel+1 AS tLevel FROM COL as c INNER JOIN COL_CTE AS ce --遞歸調用 ON c.ParentId = ce.Id ) SELECT * FROM COL_CTE
結果:
0表示頂級欄目。1就是1級欄目
排名函數
四個排名函數:
1.row_number
2.rank
3.dense_rank
4.ntile
排名函數order by子句是必需的。我們這里不講定義,直接講實例用法。
利用row_number生成連續行號
SELECT empid , qty , ROW_NUMBER() OVER ( ORDER BY qty ) AS rownum FROM sales ORDER BY qty
小的分組范圍內排序,通過PARTITION BY選項來重新排序,給數據分區或者數據區域唯一的遞增序號
如:LastName以‘A’開頭的作為第一組,在這個組內進行排序。以‘B’開頭的作為第二組,在這個組內排序。以‘C’開頭的作為第三組,在這個組內進行排序,如此等等
select ROW_NUMBER() over(PARTITION by substring(LastName,1,1) order by LastName) as RowNum, FirstName+' '+ LastName as FullName from HumanResources.vEmployee
結果
假設LastName以‘A’開頭的是男子組,這個組有共有三個人,Kim Abercrombie是冠軍,Jay Adams是亞軍,Nancy Anderson是季軍。假設LastName以‘B’開頭的是女子組,這個組只有一個人Bryan Baker,無論如何她都是冠軍。等等如此類推。這樣一眼就能看出他們的小組名次了。
RANK
果有同時撞線的情況發生應該怎么計名次呢?例如A第一個撞線,B和C同時第二個撞線,D第三個撞線,如果我們想把D的名次計為第4名應該怎么處理呢?就是說不計順序名次,只計人數。這時就可以使用RANK函數了。
在order by子句中定義的列上,如果返回一行數據與另一行具有相同的值,rank函數將給這些行賦予相同的排名數值。在排名的過程中,保持一個內部計數值,當值有所改變時,排名序號將有一個跳躍。
SELECT ROW_NUMBER() OVER ( ORDER BY Department ) AS RowNum , RANK() OVER ( ORDER BY Department ) AS Ranking , FirstName + ' ' + LastName AS FullName , Department FROM HumanResources.vEmployeeDepartment ORDER BY RowNum
rank()函數右面也要跟上一個over子句。為了看到效果我們以Department作為排序字段,可以看到RowNum作為升序連續排名,Ranking作為計同排名,當Department的值相同時,Ranking中的值保持不變,當Ranking中的值發生變化時,Ranking列中的值將跳躍到正確的排名數值。來看結果:
從這個結果中我們可以說這次馬拉松賽跑的排名是:Tengiz Kharatishvili,Zainal Arifin,Sean Chai,Karen Berge,Chris Norred並列第1,Michael Sullivan,Sharon Salavaria,Roberto Tamburello,Gail Erickson,Jossef Goldberg並列第6,如此等等。
DENSE_RANK
A第一個撞線,B和C同時第二個撞線,D第三個撞線,如果我們想把B和C的名次計位第2名,D的名次計為第3名應該怎么處理呢?就是說考慮並列名次。這里使用DENSE_RANK函數
SELECT ROW_NUMBER() OVER ( ORDER BY Department ) AS RowNum , DENSE_RANK() OVER ( ORDER BY Department ) AS Ranking , FirstName + ' ' + LastName AS FullName , Department FROM HumanResources.vEmployeeDepartment ORDER BY RowNum
結果
按照這個結果,我們可以說這次馬拉松賽跑的排名是:Tengiz Kharatishvili,Zainal Arifin,Sean Chai,Karen Berge,Chris Norred並列第1,Michael Sullivan,Sharon Salavaria,Roberto Tamburello,Gail Erickson,Jossef Goldberg,Terri Duffy並列第2,等等。
NTILE
梭羅是鉛筆的發明者,不過他沒有申請專利。據說他天賦異稟,在父親的鉛筆廠里面打包鉛筆的時候,從一堆鉛筆里面抓取一把,每次都能精確地抓到一打12支。他在森林中目測兩顆樹之間的距離,和護林員用卷尺測量的結果相差無幾。現在如果我們想從一張表中抓取多比數據,每一筆都是相同的數目,並且標明第幾組該怎么辦呢?NTILE函數提供了這個功能。
SELECT NTILE(30) OVER ( ORDER BY Department ) AS NTiles , FirstName + ' ' + LastName AS FullName , Department FROM HumanResources.vEmployeeDepartment
現在我們要抓取30個組的數據,並保證盡可能的保證每組數目相同
這個視圖中共290條數據,290/30=9.7約等於10,所以每組10條數據,如圖每一條數據都有一個組號。這個結果要比索羅精確。
練習
用CTE刪除重復數據
創建一個用於測試的表,並在該表里插入幾條數據(包括重復的數據)
/* Create Table with 7 records- 3 are duplicate records*/ CREATE TABLE DeleteDuplicateTest ( Col1 INT, Col2 INT ) INSERT INTO DeleteDuplicateTest SELECT 1 , 1 UNION ALL SELECT 1 , 1 --duplicate UNION ALL SELECT 1 , 1 --duplicate UNION ALL SELECT 1 , 2 UNION ALL SELECT 1 , 2 --duplicate UNION ALL SELECT 1 , 3 UNION ALL SELECT 1 , 4 GO
用CTE刪除重復數據4條惟一的記錄
參考SQL

WITH CTE ( COl1, Col2, DuplicateCount ) AS ( SELECT COl1 , Col2 , ROW_NUMBER() OVER ( PARTITION BY COl1, Col2 ORDER BY Col1 ) AS DuplicateCount FROM DELETEDUPLICATETEST ) DELETE FROM CTE WHERE DuplicateCount > 1 GO /*用SQL SERVER 的CTE,它將重新生成一個相同的但附加了一行編號的表。在此方案中,我們有Col1,Col2以及包含這個兩列重復數的列,對於不同的查詢,這個重復數的列可能有不同的值。另一點需要注意的是,一旦CTE被創建,DELETE語句就可以被運行了。這里我們設置一個條件——當我們讀取到的記錄大於一條(即有重復數據),我們刪除除了第一條的所有其他(這里可能有點繞,簡單的話就是保留一條重復的記錄)*/
以上部分示例總結於:T-SQL中的排名函數