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;
C#中里的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個空格
對于C#里的SQL字符串,不可有縮進,即每一行字符串不可以空格開頭
2).換行
1>.Select/From/Where/Order by/Group by等子句必須另其一行寫
2>.Select子句內(nèi)容如果只有一項,與Select同行寫
3>.Select子句內(nèi)容如果多于一項,每一項單獨占一行,在對應(yīng)Select的基礎(chǔ)上向右縮進8個空格(C#無縮進)
4>.From子句內(nèi)容如果只有一項,與From同行寫
5>.From子句內(nèi)容如果多于一項,每一項單獨占一行,在對應(yīng)From的基礎(chǔ)上向右縮進8個空格(C#無縮進)
6>.Where子句的條件如果有多項,每一個條件占一行,以AND開頭,且無縮進
7>.(Update)Set子句內(nèi)容每一項單獨占一行,無縮進
8>.Insert子句內(nèi)容每個表字段單獨占一行,無縮進;values每一項單獨占一行,無縮進
9>.SQL文中間不允許出現(xiàn)空行
10>.C#里單引號必須跟所屬的SQL子句處在同一行,連接符("+")必須在行首
3).空格
1>.SQL內(nèi)算數(shù)運算符、邏輯運算符連接的兩個元素之間必須用空格分隔
2>.逗號之后必須接一個空格
3>.關(guān)鍵字、保留字和左括號之間必須有一個空格
2.不等于統(tǒng)一使用"<>"。雖然 SQLServer認為"!="和"<>"是等價的,都代表不等于的意義。為了統(tǒng)一,不等于一律使用"<>"表示
3.使用表的別名, 數(shù)據(jù)庫查詢,必須使用表的別名。
4.SQL文對表字段擴展的兼容性。在C#里使用Select *時,嚴禁通過getString(1)的形式得到查詢結(jié)果,必須使用getString("字段名")的形式;使用Insert時,必須指定插入的字段名,嚴禁不指定字段名直接插入values
5.減少子查詢的使用。子查詢除了可讀性差之外,還在一定程度上影響了SQL運行效率,請盡量減少使用子查詢的使用,用其他效率更高、可讀性更好的方式替代?! ?
6.適當添加索引以提高查詢效率,適當添加索引可以大幅度的提高檢索速度,請參看SQLSERVER SQL性能優(yōu)化系列。
7.對數(shù)據(jù)庫表操作的特殊要求
本項目對數(shù)據(jù)庫表的操作還有以下特殊要求:
1).以邏輯刪除替代物理刪除
注意:現(xiàn)在數(shù)據(jù)庫表中數(shù)據(jù)沒有物理刪除,只有邏輯刪除
以deleteflag字段作為刪除標志,deleteflag='1'代表此記錄被邏輯刪除,因此在查詢數(shù)據(jù)時必須考慮deleteflag的因素,deleteflag的標準查詢條件:NVL(deleteflag, '0') <> '1'
2).增加記錄狀態(tài)字段
數(shù)據(jù)庫中的每張表基本都有以下字段:DELETEFLAG、UPDATECOUNT、CREDATE、CREUSER、UPDATETIME、UPDATEUSER
要注意在對標進行操作時必須考慮以下字段
插入一條記錄時要置DELETEFLAG='0', UPDATECOUNT=0, CREDATE=sysdate, CREUSER=登錄User;查詢一條記錄時要考慮DELETEFLAG,如果有可能對此記錄作更新時還要取得UPDATECOUNT作同步檢查?。恍薷囊粭l記錄時要置UPDATETIME=sysdate, UPDATEUSER=登錄User, UPDATECOUNT=(UPDATECOUNT+1) mod 1000,??;刪除一條記錄時要置DELETEFLAG='1'
3).歷史表
數(shù)據(jù)庫里部分表還存在相應(yīng)的歷史表,比如srv_twc_f和srv_twchistory_f,在查詢數(shù)據(jù)時除了檢索所在表之外,還必須檢索相應(yīng)的歷史表,對二者的結(jié)果做Union(或Union All)
8.用執(zhí)行計劃分析SQL性能
EXPLAIN PLAN是一個很好的分析SQL語句的工具,它可以在不執(zhí)行SQL的情況下分析語句。通過分析,我們就可以知道SQLSERVER是怎樣連接表,使用什么方式掃描表(索引掃描或全表掃描),以及使用到的索引名稱,按照從里到外,從上到下的次序解讀分析的結(jié)果,EXPLAIN PLAN的分析結(jié)果是用縮進的格式排列的,最內(nèi)部的操作將最先被解讀,如果兩個操作處于同一層中,帶有最小操作號的將首先被執(zhí)行。目前許多第三方的工具如PLSQL Developer和TOAD等都提供了極其方便的EXPLAIN PLAN工具,PG需要將自己添加的查詢SQL文記入log,然后在EXPLAIN PLAN中進行分析,盡量減少全表掃描,
?SQLSERVER SQL性能優(yōu)化系列
1.選擇最有效率的表名順序(只在基于規(guī)則的優(yōu)化器中有效)
SQLSERVER的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最后的表(基礎(chǔ)表driving table)將被最先處理,在FROM子句中包含多個表的情況下,必須選擇記錄條數(shù)最少的表作為基礎(chǔ)表,當SQLSERVER處理多個表時,會運用排序及合并的方式連接它們,
首先,掃描第一個表(FROM子句中最后的那個表)并對記錄進行排序;然后掃描第二個表(FROM子句中最后第二個表);最后將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合并
例如: 表 TAB1 16,384 條記錄表 TAB2 5 條記錄,選擇TAB2作為基礎(chǔ)表 (最好的方法) select count(*) from tab1,tab2 執(zhí)行時間0.96秒,選擇TAB2作為基礎(chǔ)表 (不佳的方法) select count(*) from tab2,tab1 執(zhí)行時間26.09秒;
如果有3個以上的表連接查詢,那就需要選擇交叉表(intersection table)作為基礎(chǔ)表,交叉表是指那個被其他表所引用的表
例如:
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子句中的連接順序
SQLSERVER采用自下而上的順序解析WHERE子句,根據(jù)這個原理,表之間的連接必須寫在其他WHERE條件之前,那些可以過濾掉最大數(shù)量記錄的條件必須寫在WHERE子句的末尾
例如:
(低效,執(zhí)行時間156.3秒)
SELECT *
FROM EMP E
WHERE SAL > 50000
AND JOB = 'MANAGER'
AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO);
(高效,執(zhí)行時間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時,使用動態(tài)SQL列引用'*'是一個方便的方法,不幸的是,這是一個非常低效的方法。實際上,SQLSERVER在解析的過程中,會將'*'依次轉(zhuǎn)換成所有的列名,這個工作是通過查詢數(shù)據(jù)字典完成的,這意味著將耗費更多的時間
4.減少訪問數(shù)據(jù)庫的次數(shù)。當執(zhí)行每條SQL語句時,SQLSERVER在內(nèi)部執(zhí)行了許多工作:解析SQL語句,估算索引的利用率,綁定變量,讀數(shù)據(jù)塊等等
由此可見,減少訪問數(shù)據(jù)庫的次數(shù),就能實際上減少SQLSERVER的工作量,例如:
以下有三種方法可以檢索出雇員號等于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函數(shù)來減少處理時間
使用DECODE函數(shù)可以避免重復(fù)掃描相同記錄或重復(fù)連接相同的表
例如:
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函數(shù)高效地得到相同結(jié)果
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函數(shù)也可以運用于GROUP BY和ORDER BY子句中
6.用Where子句替換HAVING子句
避免使用HAVING子句,HAVING只會在檢索出所有記錄之后才對結(jié)果集進行過濾,這個處理需要排序、統(tǒng)計等操作
如果能通過WHERE子句限制記錄的數(shù)目,那就能減少這方面的開銷
例如:
低效
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
在許多基于基礎(chǔ)表的查詢中,為了滿足一個條件,往往需要對另一個表進行聯(lián)接
在這種情況下,使用EXISTS(或NOT EXISTS)通常將提高查詢的效率
低效
SELECT *
FROM EMP (基礎(chǔ)表)
WHERE EMPNO > 0
AND DEPTNO IN (SELECT DEPTNO
FROM DEPT
WHERE LOC = 'MELB')
高效
SELECT *
FROM EMP (基礎(chǔ)表)
WHERE EMPNO > 0
AND EXISTS (SELECT 'X'
FROM DEPT
WHERE DEPT.DEPTNO = EMP.DEPTNO
AND LOC = 'MELB')
10.用NOT EXISTS替代NOT IN
在子查詢中,NOT IN子句將執(zhí)行一個內(nèi)部的排序和合并
無論在哪種情況下,NOT IN都是最低效的,因為它對子查詢中的表執(zhí)行了一個全表遍歷
為了避免使用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核心模塊將在子查詢的條件一旦滿足后,立刻返回結(jié)果
13.用索引提高效率
索引是表的一個概念部分,用來提高檢索數(shù)據(jù)的效率。實際上,SQLSERVER使用了一個復(fù)雜的自平衡B-tree結(jié)構(gòu)
通常,通過索引查詢數(shù)據(jù)比全表掃描要快。當SQLSERVER找出執(zhí)行查詢和Update語句的最佳路徑時,SQLSERVER優(yōu)化器將使用索引
同樣,在聯(lián)結(jié)多個表時使用索引也可以提高效率。另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證
除了那些LONG或LONG RAW數(shù)據(jù)類型,你可以索引幾乎所有的列
通常在大型表中使用索引特別有效,當然,在掃描小表時,使用索引同樣能提高效率
雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價
索引需要空間來存儲,也需要定期維護,每當有記錄在表中增減或索引列被修改時,索引本身也會被修改
這意味著每條記錄的INSERT、DELETE、UPDATE將為此多付出4、5次的磁盤I/O
因為索引需要額外的存儲空間和處理,那些不必要的索引反而會使查詢反應(yīng)時間變慢
SQLSERVER對索引有兩種訪問模式:
1).索引唯一掃描(INDEX UNIQUE SCAN)
大多數(shù)情況下, 優(yōu)化器通過WHERE子句訪問INDEX
例如:
表LODGING有兩個索引:建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER
SELECT *
FROM LODGING
WHERE LODGING = 'ROSE HILL';
在內(nèi)部,上述SQL將被分成兩步執(zhí)行:
首先,LODGING_PK索引將通過索引唯一掃描的方式被訪問,獲得相對應(yīng)的ROWID;然后通過ROWID訪問表的方式執(zhí)行下一步檢索
如果被檢索返回的列包括在INDEX列中,SQLSERVER將不執(zhí)行第二步的處理(通過ROWID訪問表)
因為檢索數(shù)據(jù)保存在索引中,單單訪問索引就可以完全滿足查詢結(jié)果
2).索引范圍查詢(INDEX RANGE SCAN)
適用于兩種情況:
1>.基于唯一性索引的一個范圍的檢索
2>.基于非唯一性索引的檢索
例1
SELECT LODGING
FROM LODGING
WHERE LODGING LIKE 'M%';
WHERE子句條件包括一系列值,SQLSERVER將通過索引范圍查詢的方式查詢LODGING_PK
由于索引范圍查詢將返回一組值,它的效率就要比索引唯一掃描低一些
例2
SELECT LODGING
FROM LODGING
WHERE MANAGER = 'BILL GATES';
這個SQL的執(zhí)行分兩步,LODGING$MANAGER的索引范圍查詢(得到所有符合條件記錄的ROWID),通過ROWID訪問表得到LODGING列的值
由于LODGING$MANAGER是一個非唯一性的索引,數(shù)據(jù)庫不能對它執(zhí)行索引唯一掃描
WHERE子句中,如果索引列所對應(yīng)的值的第一個字符由通配符(WILDCARD)開始,索引將不被采用
SELECT LODGING
FROM LODGING
WHERE MANAGER LIKE '%HANMAN';
在這種情況下,SQLSERVER將使用全表掃描
14.避免在索引列上使用計算
WHERE子句中,如果索引列是函數(shù)的一部分,優(yōu)化器將不使用索引而使用全表掃描
例如:
低效
SELECT …
FROM DEPT
WHERE SAL * 12 > 25000;
高效
SELECT …
FROM DEPT
WHERE SAL > 25000/12;
請務(wù)必注意,檢索中不要對索引列進行處理,如:TRIM,TO_DATE,類型轉(zhuǎn)換等操作,破壞索引,使用全表掃描,影響SQL執(zhí)行效率
15.避免在索引列上使用IS NULL和IS NOT NULL
避免在索引中使用任何可以為空的列,SQLSERVER將無法使用該索引
對于單列索引,如果列包含空值,索引中將不存在此記錄;
對于復(fù)合索引,如果每個列都為空,索引中同樣不存在此記錄。如果至少有一個列不為空,則記錄存在于索引中
如果唯一性索引建立在表的A列和B列上,并且表中存在一條記錄的A,B值為(123,null),
SQLSERVER將不接受下一條具有相同A,B值(123,null)的記錄插入
如果所有的索引列都為空,SQLSERVER將認為整個鍵值為空,而空不可能等于空,因此你可以插入1000條具有相同鍵值的記錄,當然它們都是空!
因為空值不存在于索引列中,所以WHERE子句中對索引列進行空值比較將使SQLSERVER停用該索引
低效(索引失效)
SELECT …
FROM DEPARTMENT
WHERE DEPT_CODE IS NOT NULL
16.使用UNION-ALL和UNION
當SQL語句需要UNION兩個查詢結(jié)果集合時,這兩個結(jié)果集合會以UNION-ALL的方式被合并,然后在輸出最終結(jié)果前進行排序
如果用UNION ALL替代UNION,這樣排序就不是必要了,效率就會因此得到提高
需要注意的是,UNION ALL將重復(fù)輸出兩個結(jié)果集合中相同記錄,因此還是要從業(yè)務(wù)需求分析使用UNION ALL的可行性
關(guān)于索引下列經(jīng)驗請參考:
1).如果檢索數(shù)據(jù)量超過30%的表中記錄數(shù),使用索引將沒有顯著的效率提高
2).在特定情況下,使用索引也許會比全表掃描慢,但這是同一個數(shù)量級上的差距;而通常情況下,使用索引比全表掃描要快幾倍乃至幾千倍!