開窗函數 First_Value 和 Last_Value


在Sql server 2012里面,開窗函數豐富了許多,其中帶出了2個新的函數 First_Value 和 Last Value .現在來介紹一下這2個函數的應用場景.

首先分析一下First_Value(),用法是根據Partition By對數據進行分區,如果忽略Partition By ,那么默認整塊數據一個區域,然后根據Order By 進行排序,取出第一個值。

;WITH CTE AS(
SELECT 1 AS ID ,'2016-06-01' AS DT,'A' AS UName,135 AS TotalAmount UNION ALL
SELECT 2 AS ID ,'2016-06-05' AS DT,'A' AS UName,148 AS TotalAmount UNION ALL
SELECT 3 AS ID ,'2016-06-02' AS DT,'B' AS UName,120 AS TotalAmount UNION ALL
SELECT 4 AS ID ,'2016-06-06' AS DT,'B' AS UName,153 AS TotalAmount UNION ALL
SELECT 5 AS ID ,'2016-06-10' AS DT,'B' AS UName,198 AS TotalAmount 
)
SELECT * ,
        FIRST_VALUE(CTE.TotalAmount) OVER (PARTITION BY CTE.UName ORDER BY CTE.ID) AS FirstDeal,
        FIRST_VALUE(CTE.DT) OVER (PARTITION BY CTE.UName ORDER BY CTE.ID) AS FirstDate
    FROM CTE

ID DT UName TotalAmount FirstDeal FirstDate
----------- ---------- ----- ----------- ----------- ----------
1 2016-06-01 A 135 135 2016-06-01
2 2016-06-05 A 148 135 2016-06-01
3 2016-06-02 B 120 120 2016-06-02
4 2016-06-06 B 153 120 2016-06-02
5 2016-06-10 B 198 120 2016-06-02

在這個場景里面,我求出了根據用戶名稱(UName)來進行分區,根據ID進行一個排序,求出每個用戶第一次購買商品的時間以及交易的金額。如果不使用First_Value 我們也可以換另外一種寫法

;WITH CTE AS(
SELECT 1 AS ID ,'2016-06-01' AS DT,'A' AS UName,135 AS TotalAmount UNION ALL
SELECT 2 AS ID ,'2016-06-05' AS DT,'A' AS UName,148 AS TotalAmount UNION ALL
SELECT 3 AS ID ,'2016-06-02' AS DT,'B' AS UName,120 AS TotalAmount UNION ALL
SELECT 4 AS ID ,'2016-06-06' AS DT,'B' AS UName,153 AS TotalAmount UNION ALL
SELECT 5 AS ID ,'2016-06-10' AS DT,'B' AS UName,198 AS TotalAmount 
)
SELECT * 
    FROM CTE a
        CROSS APPLY(SELECT TOP 1 a.TotalAmount AS FirstDeal,DT AS FirstDate FROM CTE WHERE a.UName = CTE.UName ORDER BY CTE.ID) AS b

--或者改寫成這種形式

;WITH CTE AS(
SELECT 1 AS ID ,'2016-06-01' AS DT,'A' AS UName,135 AS TotalAmount UNION ALL
SELECT 2 AS ID ,'2016-06-05' AS DT,'A' AS UName,148 AS TotalAmount UNION ALL
SELECT 3 AS ID ,'2016-06-02' AS DT,'B' AS UName,120 AS TotalAmount UNION ALL
SELECT 4 AS ID ,'2016-06-06' AS DT,'B' AS UName,153 AS TotalAmount UNION ALL
SELECT 5 AS ID ,'2016-06-10' AS DT,'B' AS UName,198 AS TotalAmount 
)
SELECT a.*,b.TotalAmount AS FirstDeal,b.DT AS FirstDate
    FROM CTE a
        LEFT JOIN CTE b ON a.UName = b.UName AND NOT EXISTS(SELECT * FROM CTE WHERE b.UName = UName AND DT < b.DT)

在這三種寫法里面,查詢的結果是一致的,燃鵝從查詢分析器分析的分析來看,查詢性能,使用First_Value 的效率最高,not exists 的效率其次,使用Cross Apply 效率最低。

但是這只是從查詢這么少量的測試數據反饋出來的結果,如果具體的場景需要應用,最好是結合實際情況看實際的查詢計划來得出最適當的查詢效果。

 

然后說下 Last_Value() 的用法,雖然說First_Value 和 Last_Value 一看就想兩兄弟。但是!用起來真不是這樣的一回事啊!

如果根據First_Value 的解釋,那么Last_Value 就是根據Partition進行分區,根據Order By 進行排序返回最后的一個值。想我是這樣想的,但是操作起來就不是這么一回事了。

;WITH CTE AS(
SELECT 1 AS ID ,'2016-06-01' AS DT,'A' AS UName,135 AS TotalAmount UNION ALL
SELECT 2 AS ID ,'2016-06-05' AS DT,'A' AS UName,148 AS TotalAmount UNION ALL
SELECT 3 AS ID ,'2016-06-02' AS DT,'B' AS UName,120 AS TotalAmount UNION ALL
SELECT 4 AS ID ,'2016-06-06' AS DT,'B' AS UName,153 AS TotalAmount UNION ALL
SELECT 5 AS ID ,'2016-06-10' AS DT,'B' AS UName,198 AS TotalAmount 
)
SELECT * ,
        LAST_VALUE(CTE.TotalAmount) OVER ( PARTITION BY CTE.UName ORDER BY CTE.DT) AS LR
    FROM CTE


ID          DT         UName TotalAmount LR
----------- ---------- ----- ----------- -----------
1           2016-06-01 A     135         135
2           2016-06-05 A     148         148
3           2016-06-02 B     120         120
4           2016-06-06 B     153         153
5           2016-06-10 B     198         198

 

咦!?說好的根據 UName 進行分組,然后再DT進行排序區最后一個價格呢??完全不是這樣子啊!!

對,這才是Last_Value的用法,實際上。在我測試的版本里面 (2012,2014), 除了根據 Partition By 進行分區,還對Order by 不一樣的值產生不一樣的取值。

所以,如果你想看到這個效果,我們不妨把測試樣例數據修改一下,把其中2個DT改成一樣的,如下面效果

 

;WITH CTE AS(
SELECT 1 AS ID ,'2016-06-05' AS DT,'A' AS UName,135 AS TotalAmount UNION ALL
SELECT 2 AS ID ,'2016-06-05' AS DT,'A' AS UName,148 AS TotalAmount UNION ALL
SELECT 3 AS ID ,'2016-06-02' AS DT,'B' AS UName,120 AS TotalAmount UNION ALL
SELECT 4 AS ID ,'2016-06-02' AS DT,'B' AS UName,153 AS TotalAmount UNION ALL
SELECT 5 AS ID ,'2016-06-10' AS DT,'B' AS UName,198 AS TotalAmount 
)
SELECT * ,
        LAST_VALUE(CTE.TotalAmount) OVER ( PARTITION BY CTE.UName ORDER BY CTE.DT) AS LR
    FROM CTE



ID          DT         UName TotalAmount LR
----------- ---------- ----- ----------- -----------
1           2016-06-01 A     135         135
2           2016-06-05 A     148         148
3           2016-06-02 B     120         120
4           2016-06-06 B     153         153
5           2016-06-10 B     198         198

 

so 現在就看到取值是不一樣的,燃鵝,確還是有一個問題,到底哪個才是Last_Value 呢??查詢計划說了算~(這個我還真沒驗證過,請各位大神指導一下)

所以啊,不要看名字就覺得First_Value 和 Last_Value 是親兄弟啊!!是隔壁老王的啊!!

好,本次分享到這里~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM