本文討論2點內容:
1.使用分析函數LAST_VALUE和11G LAG實現缺失數據填充。
2.LAST_VALUE和LAG在實現缺失數據填充上的區別。
1.使用分析函數LAST_VALUE和11G LAG實現缺失數據填充。
經常我們在報表中遇到這樣的問題:
例1:對每行VAL爲空的,向上找最近的不爲空的VAL,然後填充到當前爲止
dingjun123@ORADB> SELECT * FROM t; ID VAL CATE ---------- ---------- ---------- 1 VAL1 CATE0 2 CATE0 3 CATE0 4 CATE0 5 CATE0 6 VAL6 CATE1 7 CATE1 8 CATE1 9 CATE1 9 rows selected. |
在10g中有LAST_VALUE+IGNORE NULLS很好解決,如下:
dingjun123@ORADB> SELECT ID, 2 last_value(val IGNORE NULLS) over(ORDER BY ID) val, 3 cate 4 FROM t; ID VAL CATE ---------- ---------- ---------- 1 VAL1 CATE0 2 VAL1 CATE0 3 VAL1 CATE0 4 VAL1 CATE0 5 VAL1 CATE0 6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. |
上面的SQL含義是ID排序直到當前行(默認是RANGE窗口),忽略VAL爲空的值,因爲是LAST_VALUE,所以找最近的不爲空的VAL值來填充到當前行。在11G中,LAG分析函數也帶IGNORE NULLS,所以也能實現上面的功能,因爲LAG是找當前行前面1行的值,所以需要加個NVL,LAST_VALUE不需要,它是直接找到當前行,否則有值的可能爲空,如下:
dingjun123@ORADB> SELECT ID, 2 nvl(val,lag(val IGNORE NULLS) over(ORDER BY ID)) val, 3 cate 4 FROM t; ID VAL CATE ---------- ---------- ---------- 1 VAL1 CATE0 2 VAL1 CATE0 3 VAL1 CATE0 4 VAL1 CATE0 5 VAL1 CATE0 6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. |
當然,具體需求總是複雜的,如果變換一下:
例2:如果前面找不到值填充(也就是前面的全是NULL),那麼就向後查找最近的一條不爲空的值填充。如下:
dingjun123@ORADB> select id,val,cate from t; ID VAL CATE ---------- ---------- ---------- 1 CATE0 2 CATE0 3 VAL3 CATE0 4 CATE0 5 CATE0 6 VAL6 CATE1 7 CATE1 8 CATE1 9 CATE1 9 rows selected. |
對於ID=1和ID=2的行,因爲前面找不到VAL的值,所以用ID=3的來填充。很顯然,這裏需要用到2次LAST_VALUE分析函數,一次是正常用當前行前面的VAL來填充,如果填充不了,就用按ID倒敘排列的最近一行來填充。如下:
dingjun123@ORADB> SELECT ID, 2 nvl(last_value(val IGNORE NULLS) over(ORDER BY ID), 3 last_value(val IGNORE NULLS) over(ORDER BY ID DESC)) val, 4 cate 5 FROM t 6 ORDER BY ID; ID VAL CATE ---------- ---------- ---------- 1 VAL3 CATE0 2 VAL3 CATE0 3 VAL3 CATE0 4 VAL3 CATE0 5 VAL3 CATE0 6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. |
黃色區域的數據還是按向上查找的填充方式,紅色部分按照向下查找填充的方式。當然,也可以使用LAG或LEAD來實現:如下:
dingjun123@ORADB> SELECT ID, 2 nvl(val,nvl(lag(val IGNORE NULLS) over(ORDER BY ID),lag(val IGNORE NULLS) over(ORDER BY ID DESC))) val, 3 cate 4 FROM t 5 ORDER BY ID; ID VAL CATE ---------- ---------- ---------- 1 VAL3 CATE0 2 VAL3 CATE0 3 VAL3 CATE0 4 VAL3 CATE0 5 VAL3 CATE0 6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. dingjun123@ORADB> SELECT ID, 2 nvl(val,nvl(lag(val IGNORE NULLS) over(ORDER BY ID),lead(val IGNORE NULLS) over(ORDER BY ID))) val, 3 cate 4 FROM t 5 ORDER BY ID; ID VAL CATE ---------- ---------- ---------- 1 VAL3 CATE0 2 VAL3 CATE0 3 VAL3 CATE0 4 VAL3 CATE0 5 VAL3 CATE0 6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. |
有沒有注意到,使用LAG,排序是ORDER BY ID DESC,使用LEAD,則排序是ORDER BY ID。因爲LAG默認是找按照ID排序,找當前行之前的1行,LEAD則是找當前行之後的1行(都是忽略NULL後的結果)對應的值,所以它們這樣寫是等價的。但是爲什麼使用LAST_VALUE的時候,我沒有用FIRST_VALUE+ORDER BY ID呢,顯然這是不行的, LAST_VALUE是按照排序,直到找到當前行,返回最大的ID對應的值(忽略NULL),它計算的不是當前行之前的1行值,FIRST_VALUE是按照排序,找對應窗口的最小ID對應的值(忽略NULL)。所以LAST_VALUE+ORDER BY ID DESC不等價於FIRST_VALUE+ORDER BY ID。見下表格,表示兩者之間的區別:
ID | VAL | CATE | 說明 |
1 | CATE0 |
使用LAG+ORDER BY ID DESC(忽略NULL,並且是找當前行之前的最近不爲NULL的行), 則可以找到ID=3對應的行, 使用LEAD+ORDER BY ID,也可以找到ID=3的行(忽略NULL,找當前行之後最近不爲NULL的行) |
|
2 | CATE0 | ||
3 | VAL3 | CATE0 | |
4 | CATE0 | ||
5 | CATE0 | ||
6 | VAL6 | CATE1 | |
7 | CATE1 | ||
8 | CATE1 | ||
9 | CATE1 |
ID | VAL | CATE | 說明 |
1 | CATE0 |
使用LAST_VALUE+ORDER BY ID DESC(忽略NULL,找離當前行最近的ID對應的VAL值,包括當前行,正確), 則可以找到ID=3對應的行, 使用FIRST_VALUE+ORDER BY ID,(忽略NULL,找直到當前行的,因爲ID=1的前面沒有,所以必然還是NULL,ID=2的類似,當然可以+WINDOW窗口搞定) |
|
2 | CATE0 | ||
3 | VAL3 | CATE0 | |
4 | CATE0 | ||
5 | CATE0 | ||
6 | VAL6 | CATE1 | |
7 | CATE1 | ||
8 | CATE1 | ||
9 | CATE1 |
--不加WINDOW窗口,不正確 dingjun123@ORADB> SELECT ID, 2 nvl(last_value(val IGNORE NULLS) over(ORDER BY ID), 3 first_value(val IGNORE NULLS) over(ORDER BY ID)) val, 4 cate 5 FROM t 6 ORDER BY ID; ID VAL CATE ---------- ---------- ---------- 1 CATE0 2 CATE0 3 VAL3 CATE0 4 VAL3 CATE0 5 VAL3 CATE0 6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. --加WINDOW窗口的FIRST_VALUE,正確 dingjun123@ORADB> SELECT ID, 2 nvl(last_value(val IGNORE NULLS) over(ORDER BY ID), 3 first_value(val IGNORE NULLS) over(ORDER BY ID ROWS BETWEEN unbounded preceding AND unbounded following)) val, 4 cate 5 FROM t 6 ORDER BY ID; ID VAL CATE ---------- ---------- ---------- 1 VAL3 CATE0 2 VAL3 CATE0 3 VAL3 CATE0 4 VAL3 CATE0 5 VAL3 CATE0 6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. |
例3:繼續變換下需求,如果按照CATE分區域,每個區域內按照先從上面查找,找到則用最近的VAL填充,否則向下查找,用最近的VAL填充。如下:
dingjun123@ORADB> select id,val,cate from t; ID VAL CATE ---------- ---------- ---------- 1 CATE0 2 CATE0 3 VAL3 CATE0 4 CATE0 5 CATE0 6 CATE1 7 VAL7 CATE1 8 CATE1 9 CATE1 9 rows selected. |
上面的ID=6的按照前面的方法,用ID=3的填充,但是現在要按CATE分區,所以應該用ID=7的填充,則前面分析函數要加上PARTITION BY子句:
dingjun123@ORADB> SELECT ID, 2 nvl(last_value(val IGNORE NULLS) over(PARTITION BY cate ORDER BY ID), 3 last_value(val IGNORE NULLS) over( PARTITION BY cate ORDER BY ID DESC)) val, 4 cate 5 FROM t 6 ORDER BY ID; ID VAL CATE ---------- ---------- ---------- 1 VAL3 CATE0 2 VAL3 CATE0 3 VAL3 CATE0 4 VAL3 CATE0 5 VAL3 CATE0 6 VAL7 CATE1 7 VAL7 CATE1 8 VAL7 CATE1 9 VAL7 CATE1 9 rows selected. SELECT ID, nvl(val,nvl(lag(val IGNORE NULLS) over(PARTITION BY cate ORDER BY ID), lead(val IGNORE NULLS) over(PARTITION BY cate ORDER BY ID))) val, cate FROM t ORDER BY ID; --結果一樣,省略 |
2.LAST_VALUE和LAG在實現缺失數據填充上的區別。
LAST_VALUE分析可以可以帶WINDOW子句,而LAG分析函數不可以,這意味着,LAST_VALUE分析函數更強大,通過前面的例子可以看出,LAST_VALUE實現一般的缺失數據填充,不需要NVL的,而LAG還需要NVL,因爲它們的含義是完全不同的。比如要實現從之前開始找,再向後找至多2行,然後用最大的ID對缺失數據填充。如果使用LAST_VALUE,因爲現在不是找到當前行的最後一個ID對應的值了,所以,必須加NVL,否則有值也會被轉掉:
dingjun123@ORADB> SELECT ID,val, 2 nvl(val,last_value(val IGNORE NULLS) over(ORDER BY ID ROWS BETWEEN unbounded preceding AND 2 following)) new_val, 3 cate 4 FROM t; ID VAL NEW_VAL CATE ---------- ---------- ------------------------------------------- ---------- 1 VAL1 VAL1 CATE0 2 VAL1 CATE0 3 VAL1 CATE0 4 VAL6 CATE0 5 VAL6 CATE0 6 VAL6 VAL6 CATE1 7 VAL6 CATE1 8 VAL6 CATE1 9 VAL6 CATE1 9 rows selected. |
如果上面的需求使用LAG分析函數來實現,那就比較複雜了。
另外LAG/LEAD分析函數帶IGNORE NULLS是11G新特性,它的效率遠遠比LAST_VALUE要差:
先構造9999行數據如下:
dingjun123@ORADB> DROP TABLE t; Table dropped. dingjun123@ORADB> CREATE TABLE t AS SELECT LEVEL ID,decode(MOD(LEVEL,5),1,'VAL'||LEVEL) val, 2 'CATE'||(trunc((LEVEL-1)/5)) cate FROM dual CONNECT BY LEVEL<10000; Table created. dingjun123@ORADB> select count(*) cnt,count(val) cnt_val from t; CNT CNT_VAL ---------- ---------- 9999 2000 1 row selected. |
測試缺失數據填充,爲公平起見,LAST_VALUE也加上NVL:
dingjun123@ORADB> SELECT ID, 2 nvl(val,last_value(val IGNORE NULLS) over(ORDER BY ID)) val, 3 cate 4 FROM t; 9999 rows selected. Elapsed: 00:00:00.13 Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 31 consistent gets 0 physical reads 0 redo size 207607 bytes sent via SQL*Net to client 7741 bytes received via SQL*Net from client 668 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 9999 rows processed dingjun123@ORADB> SELECT ID, 2 nvl(val,lag(val IGNORE NULLS) over(ORDER BY ID)) val, 3 cate 4 FROM t; 9999 rows selected. Elapsed: 00:00:22.49 Statistics -------------------------------------------------------- 0 recursive calls 0 db block gets 31 consistent gets 0 physical reads 0 redo size 207607 bytes sent via SQL*Net to client 7741 bytes received via SQL*Net from client 668 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 9999 rows processed |
統計信息完全一樣,但是LAST_VALUE是0.13s,LAG是22.49s,效率差別太大。經過10046跟蹤,發現LAG分析函數+IGNORE NULLS大量消耗CPU,FETCH階段消耗大量CPU TIME。如下:
SELECT ID, nvl(val,lag(val IGNORE NULLS) over(ORDER BY ID)) val, cate FROM t call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.02 0 1 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 668 21.98 22.08 0 31 0 9999 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 670 21.98 22.11 0 32 0 9999 |
看來LAG/LEAD的IGNORE NULLS內部實現比較差,效率遠遠不如LAST_VALUE的IGNORE NULLS內部實現,當然不加IGNORE NULLS的LAG/LEAD效率還是不錯的,對於ORACLE新特性,一定要做足測試,慎用,期待12C能夠優化LAG/LEAD IGNORE NULLS。