【分析函數】使用分析函數LAST_VALUE或11g LAG實現缺失數據填充及其區別

轉載自:http://blog.chinaunix.net/uid-7655508-id-3736949.html 
在“使用Partitioned Outer Join實現稠化報表”這篇文章中,講述了實現稠化數據填充的方法。這篇文章和上述文章有所不同,主要講述實現對指定的空行,按照前面非空或後面非空數據進行填充。原來這種實現數據填充的方法,主要是用LAST_VALUE+IGNORE NULLS(10g)實現,在11G中LAG分析函數也支持IGNORE NULLS,但是,在性能上,他們是有區別的。
     本文討論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。見下表格,表示兩者之間的區別:
                                         LAG/LEAD
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  

                                LAST_VALUE/FIRST_VALUE
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。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章