隨筆-37  評論-58  文章-4  trackbacks-0

          SQL編寫規范

          1.書寫格式
          示例代碼:

          存儲過程SQL文書寫格式例
          select
                  c.dealerCode,
                  round(sum(c.submitSubletAmountDLR + c.submitPartsAmountDLR + c.submitLaborAmountDLR) / count(*), 2) as avg,
                  decode(null, 'x', 'xx', 'CNY')
          from (
                  select
                          a.dealerCode,
                          a.submitSubletAmountDLR,
                          a.submitPartsAmountDLR,
                          a.submitLaborAmountDLR
                  from SRV_TWC_F a
                  where (to_char(a.ORIGSUBMITTIME, 'yyyy/mm/dd') >= 'Date Range(start)'
                  and to_char(a.ORIGSUBMITTIME, 'yyyy/mm/dd') <= 'Date Range(end)'  
                  and nvl(a.deleteflag, '0') <> '1')
                  union all
                  select
                          b.dealerCode,
                          b.submitSubletAmountDLR,
                          b.submitPartsAmountDLR,
                          b.submitLaborAmountDLR
                  from SRV_TWCHistory_F b
                  where (to_char(b.ORIGSUBMITTIME, 'yyyy/mm/dd') >= 'Date Range(start)'
                  and to_char(b.ORIGSUBMITTIME,'yyyy/mm/dd') <= 'Date Range(end)'  
                  and nvl(b.deleteflag,'0') <> '1')
          ) c
          group by c.dealerCode
          order by avg desc;

          Java source里的SQL字符串書寫格式例
          strSQL = "insert into Snd_FinanceHistory_Tb "
              + "(DEALERCODE, "
              + "REQUESTSEQUECE, "
              + "HANDLETIME, "
              + "JOBFLAG, "
              + "FRAMENO, "
              + "INMONEY, "
              + "REMAINMONEY, "
              + "DELETEFLAG, "
              + "UPDATECOUNT, "
              + "CREUSER, "
              + "CREDATE, "
              + "HONORCHECKNO, "
              + "SEQ) "
              + "values ('" + draftInputDetail.dealerCode + "', "
              + "'" + draftInputDetail.requestsequece + "', "
              + "sysdate, "
              + "'07', "
              + "'" + frameNO + "', "
              + requestMoney + ", "
              + remainMoney + ", "
              + "'0', "
              + "0, "
              + "'" + draftStruct.employeeCode + "', "
              + "sysdate, "
              + "'" + draftInputDetail.honorCheckNo + "', "
              + index + ")";

            1).縮進
              對于存儲過程文件,縮進為8個空格
              對于Java source里的SQL字符串,不可有縮進,即每一行字符串不可以空格開頭

            2).換行
              1>.Select/From/Where/Order by/Group by等子句必須另其一行寫
              2>.Select子句內容如果只有一項,與Select同行寫
              3>.Select子句內容如果多于一項,每一項單獨占一行,在對應Select的基礎上向右縮進8個空格(Java source無縮進)
              4>.From子句內容如果只有一項,與From同行寫
              5>.From子句內容如果多于一項,每一項單獨占一行,在對應From的基礎上向右縮進8個空格(Java source無縮進)
              6>.Where子句的條件如果有多項,每一個條件占一行,以AND開頭,且無縮進
              7>.(Update)Set子句內容每一項單獨占一行,無縮進
              8>.Insert子句內容每個表字段單獨占一行,無縮進;values每一項單獨占一行,無縮進
              9>.SQL文中間不允許出現空行
              10>.Java source里單引號必須跟所屬的SQL子句處在同一行,連接符("+")必須在行首

            3).空格
              1>.SQL內算數運算符、邏輯運算符連接的兩個元素之間必須用空格分隔
              2>.逗號之后必須接一個空格
              3>.關鍵字、保留字和左括號之間必須有一個空格

          2.不等于統一使用"<>"
            Oracle認為"!="和"<>"是等價的,都代表不等于的意義。為了統一,不等于一律使用"<>"表示

          3.使用表的別名
            數據庫查詢,必須使用表的別名

          4.SQL文對表字段擴展的兼容性
            在Java source里使用Select *時,嚴禁通過getString(1)的形式得到查詢結果,必須使用getString("字段名")的形式
            使用Insert時,必須指定插入的字段名,嚴禁不指定字段名直接插入values

          5.減少子查詢的使用
            子查詢除了可讀性差之外,還在一定程度上影響了SQL運行效率
            請盡量減少使用子查詢的使用,用其他效率更高、可讀性更好的方式替代

          6.適當添加索引以提高查詢效率
            適當添加索引可以大幅度的提高檢索速度
            請參看ORACLE SQL性能優化系列

          7.對數據庫表操作的特殊要求
            本項目對數據庫表的操作還有以下特殊要求:

            1).以邏輯刪除替代物理刪除
              注意:現在數據庫表中數據沒有物理刪除,只有邏輯刪除
              以deleteflag字段作為刪除標志,deleteflag='1'代表此記錄被邏輯刪除,因此在查詢數據時必須考慮deleteflag的因素
              deleteflag的標準查詢條件:NVL(deleteflag, '0') <> '1'

            2).增加記錄狀態字段
              數據庫中的每張表基本都有以下字段:DELETEFLAG、UPDATECOUNT、CREDATE、CREUSER、UPDATETIME、UPDATEUSER
              要注意在對標進行操作時必須考慮以下字段

              插入一條記錄時要置DELETEFLAG='0', UPDATECOUNT=0, CREDATE=sysdate, CREUSER=登錄User
              查詢一條記錄時要考慮DELETEFLAG,如果有可能對此記錄作更新時還要取得UPDATECOUNT作同步檢查
              修改一條記錄時要置UPDATETIME=sysdate, UPDATEUSER=登錄User, UPDATECOUNT=(UPDATECOUNT+1) mod 1000,
              刪除一條記錄時要置DELETEFLAG='1'

            3).歷史表
              數據庫里部分表還存在相應的歷史表,比如srv_twc_f和srv_twchistory_f
              在查詢數據時除了檢索所在表之外,還必須檢索相應的歷史表,對二者的結果做Union(或Union All)

          8.用執行計劃分析SQL性能
            EXPLAIN PLAN是一個很好的分析SQL語句的工具,它可以在不執行SQL的情況下分析語句
            通過分析,我們就可以知道ORACLE是怎樣連接表,使用什么方式掃描表(索引掃描或全表掃描),以及使用到的索引名稱

            按照從里到外,從上到下的次序解讀分析的結果
            EXPLAIN PLAN的分析結果是用縮進的格式排列的,最內部的操作將最先被解讀,如果兩個操作處于同一層中,帶有最小操作號的將首先被執行

            目前許多第三方的工具如PLSQL Developer和TOAD等都提供了極其方便的EXPLAIN PLAN工具
            PG需要將自己添加的查詢SQL文記入log,然后在EXPLAIN PLAN中進行分析,盡量減少全表掃描


          ORACLE SQL性能優化系列

          1.選擇最有效率的表名順序(只在基于規則的優化器中有效)
             ORACLE的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最后的表(基礎表driving table)將被最先處理
             在FROM子句中包含多個表的情況下,必須選擇記錄條數最少的表作為基礎表
             當ORACLE處理多個表時,會運用排序及合并的方式連接它們
             首先,掃描第一個表(FROM子句中最后的那個表)并對記錄進行排序;
             然后掃描第二個表(FROM子句中最后第二個表);
             最后將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合并
             例如:
               表 TAB1 16,384 條記錄
               表 TAB2 5 條記錄
               選擇TAB2作為基礎表 (最好的方法)
               select count(*) from tab1,tab2 執行時間0.96秒
               選擇TAB2作為基礎表 (不佳的方法)
               select count(*) from tab2,tab1 執行時間26.09秒

             如果有3個以上的表連接查詢,那就需要選擇交叉表(intersection table)作為基礎表,交叉表是指那個被其他表所引用的表
             例如:
               EMP表描述了LOCATION表和CATEGORY表的交集
                 SELECT *
                 FROM LOCATION L,
                 CATEGORY C,
                 EMP E
                 WHERE E.EMP_NO BETWEEN 1000 AND 2000
                 AND E.CAT_NO = C.CAT_NO
                 AND E.LOCN = L.LOCN

               將比下列SQL更有效率
                 SELECT *
                 FROM EMP E ,
                 LOCATION L ,
                 CATEGORY C
                 WHERE E.CAT_NO = C.CAT_NO
                 AND E.LOCN = L.LOCN
                 AND E.EMP_NO BETWEEN 1000 AND 2000

          2.WHERE子句中的連接順序
            ORACLE采用自下而上的順序解析WHERE子句
            根據這個原理,表之間的連接必須寫在其他WHERE條件之前,那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾
            例如:
               (低效,執行時間156.3秒)
                 SELECT *
                 FROM EMP E
                 WHERE SAL > 50000
                 AND JOB = 'MANAGER'
                 AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO);

               (高效,執行時間10.6秒)
                 SELECT *
                 FROM EMP E
                 WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO)
                 AND SAL > 50000
                 AND JOB = 'MANAGER';

          3.SELECT子句中避免使用'*'
            當你想在SELECT子句中列出所有的COLUMN時,使用動態SQL列引用'*'是一個方便的方法,不幸的是,這是一個非常低效的方法
            實際上,ORACLE在解析的過程中,會將'*'依次轉換成所有的列名
            這個工作是通過查詢數據字典完成的,這意味著將耗費更多的時間

          4.減少訪問數據庫的次數
            當執行每條SQL語句時,ORACLE在內部執行了許多工作:解析SQL語句,估算索引的利用率,綁定變量,讀數據塊等等
            由此可見,減少訪問數據庫的次數,就能實際上減少ORACLE的工作量
            例如:
              以下有三種方法可以檢索出雇員號等于0342或0291的職員
              方法1 (最低效)
                SELECT EMP_NAME, SALARY, GRADE
                FROM EMP
                WHERE EMP_NO = 342;

                SELECT EMP_NAME, SALARY, GRADE
                FROM EMP
                WHERE EMP_NO = 291;

              方法2 (次低效)
                DECLARE
                CURSOR C1 (E_NO NUMBER) IS
                SELECT EMP_NAME,SALARY,GRADE
                FROM EMP
                WHERE EMP_NO = E_NO;
                BEGIN
                OPEN C1(342);
                FETCH C1 INTO …,…,…;
                …
                OPEN C1(291);
                FETCH C1 INTO …,…,…;
                …
                CLOSE C1;
                END;                                  

              方法2 (高效)
                SELECT A.EMP_NAME, A.SALARY, A.GRADE,
                       B.EMP_NAME, B.SALARY, B.GRADE
                FROM EMP A, EMP B
                WHERE A.EMP_NO = 342
                AND B.EMP_NO = 291;

          5.使用DECODE函數來減少處理時間
            使用DECODE函數可以避免重復掃描相同記錄或重復連接相同的表
            例如:
              SELECT COUNT(*), SUM(SAL)
              FROM EMP
              WHERE DEPT_NO = '0020'
              AND ENAME LIKE 'SMITH%';

              SELECT COUNT(*), SUM(SAL)
              FROM EMP
              WHERE DEPT_NO = '0030'
              AND ENAME LIKE 'SMITH%';

              你可以用DECODE函數高效地得到相同結果
              SELECT COUNT(DECODE(DEPT_NO, '0020', 'X', NULL)) D0020_COUNT,
              COUNT(DECODE(DEPT_NO, '0030', 'X', NULL)) D0030_COUNT,
              SUM(DECODE(DEPT_NO, '0020', SAL, NULL)) D0020_SAL,
              SUM(DECODE(DEPT_NO, 0030, SAL, NULL)) D0030_SAL
              FROM EMP
              WHERE ENAME LIKE 'SMITH%';

            'X'表示任何一個字段
            類似的,DECODE函數也可以運用于GROUP BY和ORDER BY子句中

          6.用Where子句替換HAVING子句
            避免使用HAVING子句,HAVING只會在檢索出所有記錄之后才對結果集進行過濾,這個處理需要排序、統計等操作
            如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷
            例如:
              低效
              SELECT REGION, AVG(LOG_SIZE)
              FROM LOCATION
              GROUP BY REGION
              HAVING REGION REGION != 'SYDNEY'
              AND REGION != 'PERTH'

              高效
              SELECT REGION, AVG(LOG_SIZE)
              FROM LOCATION
              WHERE REGION REGION != 'SYDNEY'
              AND REGION != 'PERTH'
              GROUP BY REGION

          7.減少對表的查詢
            在含有子查詢的SQL語句中,要特別注意減少對表的查詢
            例如:
              低效
              SELECT TAB_NAME
              FROM TABLES
              WHERE TAB_NAME = (SELECT TAB_NAME
                                FROM TAB_COLUMNS
                                WHERE VERSION = 604)
              AND DB_VER = (SELECT DB_VER
                            FROM TAB_COLUMNS
                            WHERE VERSION = 604)

              高效
              SELECT TAB_NAME
              FROM TABLES
              WHERE (TAB_NAME, DB_VER) = (SELECT TAB_NAME, DB_VER
                                          FROM TAB_COLUMNS
                                          WHERE VERSION = 604)

            Update多個Column例子:
              低效
              UPDATE EMP
              SET EMP_CAT = (SELECT MAX(CATEGORY)
                             FROM EMP_CATEGORIES),
              SAL_RANGE = (SELECT MAX(SAL_RANGE)
                           FROM EMP_CATEGORIES)
              WHERE EMP_DEPT = 0020;

              高效
              UPDATE EMP
              SET (EMP_CAT, SAL_RANGE) = (SELECT MAX(CATEGORY), MAX(SAL_RANGE)
                                          FROM EMP_CATEGORIES)
              WHERE EMP_DEPT = 0020;

          8.使用表的別名(Alias)
            當在SQL語句中連接多個表時,請使用表的別名并把別名前綴于每個Column上
            這樣可以減少解析的時間并減少那些由Column歧義引起的語法錯誤

          9.用EXISTS替代IN                                   
            在許多基于基礎表的查詢中,為了滿足一個條件,往往需要對另一個表進行聯接
            在這種情況下,使用EXISTS(或NOT EXISTS)通常將提高查詢的效率
              低效
              SELECT *
              FROM EMP (基礎表)
              WHERE EMPNO > 0
              AND DEPTNO IN (SELECT DEPTNO
                             FROM DEPT
                             WHERE LOC = 'MELB')

              高效
              SELECT *
              FROM EMP (基礎表)
              WHERE EMPNO > 0
              AND EXISTS (SELECT 'X'
              FROM DEPT
              WHERE DEPT.DEPTNO = EMP.DEPTNO
              AND LOC = 'MELB')

          10.用NOT EXISTS替代NOT IN
            在子查詢中,NOT IN子句將執行一個內部的排序和合并
            無論在哪種情況下,NOT IN都是最低效的,因為它對子查詢中的表執行了一個全表遍歷
            為了避免使用NOT IN,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS
            例如:
              SELECT …
              FROM EMP
              WHERE DEPT_NO NOT IN (SELECT DEPT_NO
                                    FROM DEPT
                                    WHERE DEPT_CAT = 'A');

              為了提高效率改寫為
              高效
              SELECT …
              FROM EMP A, DEPT B
              WHERE A.DEPT_NO = B.DEPT(+)
              AND B.DEPT_NO IS NULL
              AND B.DEPT_CAT(+) = 'A'

              最高效
              SELECT …
              FROM EMP E
              WHERE NOT EXISTS (SELECT 'X'
                                FROM DEPT D
                                WHERE D.DEPT_NO = E.DEPT_NO
                                AND DEPT_CAT = 'A');

          11.用表連接替換EXISTS
            通常來說,采用表連接的方式比EXISTS更有效率
            例如:
              SELECT ENAME
              FROM EMP E
              WHERE EXISTS (SELECT 'X'
                            FROM DEPT
                            WHERE DEPT_NO = E.DEPT_NO
                            AND DEPT_CAT = 'A');

              更高效
              SELECT ENAME
              FROM DEPT D, EMP E
              WHERE E.DEPT_NO = D.DEPT_NO
              AND DEPT_CAT = 'A';

          12.用EXISTS替換DISTINCT
            當提交一個包含多表信息(比如部門表和雇員表)的查詢時,避免在SELECT子句中使用DISTINCT,一般可以考慮用EXIST替換
            例如:
              低效
              SELECT DISTINCT DEPT_NO, DEPT_NAME
              FROM DEPT D, EMP E
              WHERE D.DEPT_NO = E.DEPT_NO

              高效
              SELECT DEPT_NO, DEPT_NAME
              FROM DEPT D
              WHERE EXISTS (SELECT 'X'
                            FROM EMP E
                            WHERE E.DEPT_NO = D.DEPT_NO);

            EXISTS使查詢更為迅速,因為RDBMS核心模塊將在子查詢的條件一旦滿足后,立刻返回結果

          13.用索引提高效率
            索引是表的一個概念部分,用來提高檢索數據的效率。實際上,ORACLE使用了一個復雜的自平衡B-tree結構
            通常,通過索引查詢數據比全表掃描要快。當ORACLE找出執行查詢和Update語句的最佳路徑時,ORACLE優化器將使用索引
            同樣,在聯結多個表時使用索引也可以提高效率。另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證

            除了那些LONG或LONG RAW數據類型,你可以索引幾乎所有的列
            通常在大型表中使用索引特別有效,當然,在掃描小表時,使用索引同樣能提高效率

            雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價
            索引需要空間來存儲,也需要定期維護,每當有記錄在表中增減或索引列被修改時,索引本身也會被修改
            這意味著每條記錄的INSERT、DELETE、UPDATE將為此多付出4、5次的磁盤I/O
            因為索引需要額外的存儲空間和處理,那些不必要的索引反而會使查詢反應時間變慢

            ORACLE對索引有兩種訪問模式:
            1).索引唯一掃描(INDEX UNIQUE SCAN)
              大多數情況下, 優化器通過WHERE子句訪問INDEX
              例如:
                表LODGING有兩個索引:建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER
                SELECT *
                FROM LODGING
                WHERE LODGING = 'ROSE HILL';

                在內部,上述SQL將被分成兩步執行:
                首先,LODGING_PK索引將通過索引唯一掃描的方式被訪問,獲得相對應的ROWID;然后通過ROWID訪問表的方式執行下一步檢索
                如果被檢索返回的列包括在INDEX列中,ORACLE將不執行第二步的處理(通過ROWID訪問表)
                因為檢索數據保存在索引中,單單訪問索引就可以完全滿足查詢結果

            2).索引范圍查詢(INDEX RANGE SCAN)
              適用于兩種情況:
              1>.基于唯一性索引的一個范圍的檢索
              2>.基于非唯一性索引的檢索
              例1
                SELECT LODGING
                FROM LODGING
                WHERE LODGING LIKE 'M%';

                WHERE子句條件包括一系列值,ORACLE將通過索引范圍查詢的方式查詢LODGING_PK
                由于索引范圍查詢將返回一組值,它的效率就要比索引唯一掃描低一些

              例2
                SELECT LODGING
                FROM LODGING
                WHERE MANAGER = 'BILL GATES';

                這個SQL的執行分兩步,LODGING$MANAGER的索引范圍查詢(得到所有符合條件記錄的ROWID),通過ROWID訪問表得到LODGING列的值
                由于LODGING$MANAGER是一個非唯一性的索引,數據庫不能對它執行索引唯一掃描

            WHERE子句中,如果索引列所對應的值的第一個字符由通配符(WILDCARD)開始,索引將不被采用
              SELECT LODGING
              FROM LODGING
              WHERE MANAGER LIKE '%HANMAN';

            在這種情況下,ORACLE將使用全表掃描

          14.避免在索引列上使用計算
            WHERE子句中,如果索引列是函數的一部分,優化器將不使用索引而使用全表掃描
            例如:
              低效
              SELECT …
              FROM DEPT
              WHERE SAL * 12 > 25000;

              高效
              SELECT …
              FROM DEPT
              WHERE SAL > 25000/12;

            請務必注意,檢索中不要對索引列進行處理,如:TRIM,TO_DATE,類型轉換等操作,破壞索引,使用全表掃描,影響SQL執行效率

          15.避免在索引列上使用IS NULL和IS NOT NULL
            避免在索引中使用任何可以為空的列,ORACLE將無法使用該索引
            對于單列索引,如果列包含空值,索引中將不存在此記錄;
            對于復合索引,如果每個列都為空,索引中同樣不存在此記錄。如果至少有一個列不為空,則記錄存在于索引中

            如果唯一性索引建立在表的A列和B列上,并且表中存在一條記錄的A,B值為(123,null),
            ORACLE將不接受下一條具有相同A,B值(123,null)的記錄插入
            如果所有的索引列都為空,ORACLE將認為整個鍵值為空,而空不可能等于空,因此你可以插入1000條具有相同鍵值的記錄,當然它們都是空!
            因為空值不存在于索引列中,所以WHERE子句中對索引列進行空值比較將使ORACLE停用該索引

            低效(索引失效)
            SELECT …
            FROM DEPARTMENT
            WHERE DEPT_CODE IS NOT NULL

          16.使用UNION-ALL和UNION
            當SQL語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合并,然后在輸出最終結果前進行排序
            如果用UNION ALL替代UNION,這樣排序就不是必要了,效率就會因此得到提高

            需要注意的是,UNION ALL將重復輸出兩個結果集合中相同記錄,因此還是要從業務需求分析使用UNION ALL的可行性


          關于索引下列經驗請參考:
          1).如果檢索數據量超過30%的表中記錄數,使用索引將沒有顯著的效率提高
          2).在特定情況下,使用索引也許會比全表掃描慢,但這是同一個數量級上的差距;而通常情況下,使用索引比全表掃描要快幾倍乃至幾千倍!

          其他具體內容請參考《ORACLE SQL性能優化系列》

          17.使用PrepareStatement
          在同一個方法中,當循環使用SQL文時,為了提高性能,
          請使用PreparedStatement。注意,
          僅限使用于少數的模塊。
          方法如下:

          ? PreparedStatement stmt
             = conn.prepareStatement("select a from TABLE_A where b=? c=?");

           for(?? ){
          ???? stmt.setInt(1, varB);     
          ???? stmt.setString(2, varC);     
          ?   ResultSet rst = stmt.executeQuery();

           }

          posted on 2007-09-30 13:07 楓中玎玲 閱讀(2573) 評論(0)  編輯  收藏 所屬分類: 經典轉貼
          主站蜘蛛池模板: 盘锦市| 岑溪市| 延庆县| 都昌县| 若尔盖县| 海兴县| 阳江市| 建水县| 平果县| 天柱县| 敖汉旗| 观塘区| 雷波县| 铜川市| 揭西县| 资兴市| 宁化县| 陈巴尔虎旗| 当阳市| 温州市| 威远县| 砀山县| 寿阳县| 北辰区| 郧西县| 筠连县| 鹤庆县| 集贤县| 方城县| 瑞丽市| 拉萨市| 泸西县| 贵阳市| 游戏| 武陟县| 荆州市| 北安市| 峨山| 克拉玛依市| 响水县| 客服|