1. 程式人生 > >[DB][ORACLE]SQL中使用WITH AS提高效能和可讀性

[DB][ORACLE]SQL中使用WITH AS提高效能和可讀性

 

利用WITH AS子句提高SQL的效能和可讀性

一、 原理

        WITH AS短語,也叫做子查詢部分(subquery factoring),可以讓你做很多事情,定義一個SQL片斷,該SQL片斷會被整個SQL語句所用到。

        有的時候,是為了讓SQL語句的可讀性更高些,也有可能是在UNION ALL的不同部分,作為提供資料的部分。
特別對於UNION ALL比較有用。因為UNION ALL的每個部分可能相同,但是如果每個部分都去執行一遍的話,則成本太高,所以可以使用WITH AS短語,則只要執行一遍即可。如果WITH AS短語所定義的表名被呼叫兩次以上,則優化器會自動將WITH AS短語所獲取的資料放入一個TEMP表裡,如果只是被呼叫一次,則不會。而提示materialize則是強制將WITH AS短語裡的資料放入一個全域性臨時表裡。很多查詢通過這種方法都可以提高速度。

        WITH AS 子句的功能是定義SQL語句級的臨時表、該臨時表僅對本次執行的SQL有效。作用一、可以把複雜SQL語句按照特定的業務邏輯分成幾個WITH AS臨時表、再用這些臨時表組成完整的SQL語句,從而提高SQL語句的可讀性和編寫。作用二、把一大堆重複用到的SQL語句放在with as 裡面,取一個別名,後面的查詢就可以用它、從而減少SQL語句的長度、從而大幅提高SQL的執行效率和可讀性。

二、利用WITH AS 字句提高SQL語句的執行效能 

1. 案例起因
    公司門店應用程式每天都要出一份報表,用來統計所有商品當天的期初庫存數量、入庫數量、出庫數量
及當天的期末庫存數量。執行半年以後,這份報表執行越來越慢,到現在,每次執行該報表顯示當天資料時需要近20秒的時間。於是開發人員找到我,希望我看看,是不是可以使該報表執行的時間更短。
該報表就是一段SQL語句,主要由三部分組成,第一部分是計算每個商品的期初數量,第二部分是計算每個商品的當天發生(包括入庫和出庫的)數量,第三部分是計算每個商品的期末數量,也就是當天的餘額。每個部分使用UNION ALL連線起來。
我看到該報表,第一個感覺就是這段SQL裡的每個部分都要對錶進行掃描,明顯成本過高。應該可以使用WITH AS進行改寫。

2. 案例說明
  首先介紹該SQL所涉及到的主要的表的結構。該表表名為fin,用來存放每天每個商品的發生數以及該商
品的餘額數。其表結構為如下所示(這裡我只選取了與我們要討論的SQL相關的部分表字段)。
SQL> desc fin
名稱 是否為空? 型別
----------------------------------------- -------- ----------------------------
。。。。。。
DAY DATE
SKU VARCHAR2(8)
INQTY NUMBER(16,6)
OUTQTY NUMBER(16,6)
LASTQTY NUMBER(16,6)
。。。。。。。。

簡單解釋一下各個欄位的含義:
1) DAY:發生的日期。
2) SKU:發生交易的商品程式碼。
3) INQTY:商品入庫數量。
4) OUTQTY:商品出庫數量。
5) LASTQTY:商品的餘額數量。

該表中含有的記錄數量為:
SQL> SELECT count(*) FROM fin;

COUNT(*)
----------
4729319
原來的SQL如下所示(比如查詢2003年7月14日這天的記錄。當然,我對該SQL做了些修改,去掉了與本文討論無關的部分,比如顯示商品名稱之類的部分等):

SELECT SKU,
       SUM(INITQTY) AS INITQTY,
       SUM(INQTY) AS INQTY,
       SUM(OUTQTY) AS OUTQTY,
       SUM(LASTQTY) AS LASTQTY
  FROM (SELECT SKU,
               LASTQTY AS INITQTY,
               0       AS INQTY,
               0       AS OUTQTY,
               0       AS LASTQTY
          FROM FIN
         WHERE DAY = TO_DATE('20030713', 'yyyymmdd')
        UNION ALL
        SELECT SKU, 0 AS INITQTY, INQTY, OUTQTY, 0 AS LASTQTY
          FROM FIN
         WHERE DAY >= TO_DATE('20030714', 'yyyymmdd')
           AND DAY <= TO_DATE('20030714', 'yyyymmdd')
        UNION ALL
        SELECT SKU, 0 AS INITQTY, 0 AS INQTY, 0 AS OUTQTY, LASTQTY
          FROM FIN
         WHERE DAY = TO_DATE('20030714', 'yyyymmdd'))
 GROUP BY SKU

我們來看該SQL所花費的時間為:
SQL> set timing on
SQL> /
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
SKU INITQTY INQTY OUTQTY LASTQTY
-------- ---------- ---------- ---------- ----------
00106162 0 0 12 60
00106467 0 20 10 60
已選擇956行。

已用時間: 00: 00: 19.08

然後,我們來對該SQL進行改寫一番,如下所示:

WITH RESULT AS
 (SELECT /*+ materialize */
   DAY, SKU, INQTY, OUTQTY, LASTQTY
    FROM FIN
   WHERE DAY >= TO_DATE('20030713', 'yyyymmdd')
     AND DAY <= TO_DATE('20030714', 'yyyymmdd'))
SELECT SKU, SUM(INITQTY) AS INITQTY, SUM(INQTY) AS INQTY, SUM(OUTQTY) AS OUTQTY, SUM(LASTQTY) AS LASTQTY
  FROM (SELECT SKU,
               LASTQTY AS INITQTY,
               0       AS INQTY,
               0       AS OUTQTY,
               0       AS LASTQTY
          FROM RESULT
         WHERE DAY = TO_DATE('20030713', 'yyyymmdd')
        UNION ALL
        SELECT SKU, 0 AS INITQTY, INQTY, OUTQTY, 0 AS LASTQTY
          FROM RESULT
         WHERE DAY = TO_DATE('20030714', 'yyyymmdd')
        UNION ALL
        SELECT SKU, 0 AS INITQTY, 0 AS INQTY, 0 AS OUTQTY, LASTQTY
          FROM RESULT
         WHERE DAY = TO_DATE('20030714', 'yyyymmdd'))
 GROUP BY SKU

我們來看修改後的SQL所花費的時間為:
SQL> set timing on
SQL> /
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
SKU INITQTY INQTY OUTQTY LASTQTY
-------- ---------- ---------- ---------- ----------
00106162 0 0 12 60
00106467 0 20 10 60
已選擇956行。

已用時間: 00: 00: 06.06

從這裡可以看到,通過WITH AS可以從20秒降低到6秒,幾乎提高了65%的效能。


三、利用WITH AS 字句提高SQL語句的可讀性 

WITH WD AS
 (SELECT DID, ARG(SALARY) 平均工資 FROM WORK GROUP BY DID),
EM AS
 (SELECT EMP.*, W.SALARY FROM EMP LEFT JOIN WORK W ON EMP.EID = W.EID)
SELECT * FROM WD, EM WHERE WD.DID = EM.DID AND WD.平均工資 > EM.SALARY
################################