SQLMAP編寫規範以及一些常見的SQL問題

1.select語句中的*號問題

  多表連接必須杜絕使用select *

  單表連接,一般情況不建議使用select *,但是如果出現以下情況,必須禁用:

  1)表中包含lob字段(BLOB,CLOB,LONG,RAW等)

  2)表中包含長度較大的字段,如varchar2(1000)以上的字段,但該SQL實際並不需要取出該字段的值

  3)字段數量較多,但實際使用的字段很少,比如表有50個字段,而實際需要使用的只有5個,並且該SQL沒有被重用

2.嚴格要求使用正確類型的變量,杜絕oracle做隱式類型轉換的情況

  1)推薦在sqlmap的變量中制定變量的數據類型,如:

       select * from tablename where id = #id:VARCHAR#

  2)對於時間類型的字段,必須使用TO_DATE進行賦值(當前時間可直接用sysdate表示)

       錯誤的寫法(使用date類型的變量):

       select * from tablename where id = #id:varchar#

          and dt >= #dateBegin:date#

          and dt < #dateEnd:date#

       錯誤的寫法(使用包含sysdate的表達式):

       select * from tablename where id= #id:varchar#

          and dt >= trunc(sysdate - 1)

          and dt < sysdate + 1

       錯誤的寫法(將to_date函數和數字進行算術運算):

       select * from tablename where id = #id:varchar#

          and dt >= to_date(#dateBegin:varchar#, 'yyyy-mm-dd hh24:mi:ss')

          and dt < to_date(#dateBegin:varchar#, 'yyyy-mm-dd hh24:mi:ss') + 1

       正確的寫法:

       select * from tablename where id = #id:varchar#

          and dt >= to_date(#dateBegin:varchar#, 'yyyy-mm-dd hh24:mi:ss')

          and dt < to_date(#dateEnd:varchar#, 'yyyy-mm-dd hh24:mi:ss') /*或 dt < sysdate */

3.杜絕循環調用

 

例如:在迭代的過程中,使用同一SQL反覆查詢DB,如:

while
 (listObj.hasnext()) {
SELECT * FROM process_table WHERE (no = ?)
......
}

   這樣不僅效率不高,還造成交互過於頻繁,嚴重情況會導致服務器LOAD增加
   解決方式:
   如果該查詢是使用唯一鍵(如上例),參考後面的STR2VARLIST或STR2NUMLIST的用法

4.綁定變量和替代變量

在Ibatis中:
綁定變量用 #變量名# 表示
替代變量用 $變量名$ 表示

注意幾點:
1)通常,應使用綁定變量,尤其是具體取值變化範圍較大的變量,如id = #id#。
2)取值範圍很小(比如枚舉字段),並且通常取值會比較固定,在DBA預先同意的情況下使用替代變量,或者乾脆使用常量。
3)當一個綁定變量在實際使用中實際取值總是爲某一固定常量時,應當直接使用常量而不是變量
4)在order by子句中,通常使用替代變量而不是綁定變量。
5)IN子句,使用"iterate + 數組類型變量"的方式實現綁定變量,例如:

<isNotEmpty prepend="and"
 property="userIds"
>
<iterate property="Ids" open="t.creator in (" close=")" conjunction="," >
#Ids[]#
</iterate>
</isNotEmpty>

將生成 t.creator in (:1, :2, :3, :4, :5 ...) 的語句

5.在字段上加函數的問題

1)通常,不允許在字段上添加函數或者表達式,如:

   錯誤的寫法:

select * from tableName where to_char ( dt, 'yyyy-mm-dd') = '2011-03-04';
select qty from tableName where id + 12 = 168;

   正確的寫法:

select * from tableName  where dt >= to_date ( '2007-04-04', 'yyyy-mm-dd') and dt < to_date ( '2007-04-05', 'yyyy-mm-dd');
select qty from tableName where id = 168 - 12;

2)特別注意,當表連接時,用於連接的兩個表的字段如果數據類型不一致,則必須在一邊加上類型轉換的函數,如

   錯誤的寫法(a.id是number類型,而b.operator_number是char類型):

select count(*) from tableName1 a, tableName2 b where a.id = b.operator_number and a.username = '小釵';

   正確的寫法:

select count(*) from tableName1 a, tableName2 b where to_char(a.id) = b.operator_number and a.username = '小釵';
select count(*) from tableName1 a, tableName2 b where a.id = to_number(b.operator_number) and a.username = '小釵';

6.表連接

   不使用ANSI連接,如inner join、left join、right join、full outer join,而使用(+)來表示外連接

   錯誤的寫法:

select a.*, b.goods_title from tableName1 a left join tableName2 b on a.NO = b.no
where a.code = '1234' and a.name = #name:varchar# and a.dt > to_date(...)

   正確的寫法:

select a.*, b.goods_title from tableName a, tableName b
where a.NO = b.no(+) and a.code = '1234' and a.name = #name:varchar# and a.dt > to_date(...)

7.SQLMAP的其它編寫規範

1)對錶的記錄進行更新的時候,必須包含對gmt_modified字段的更新,並且不要使用dynamic標記,如:

   錯誤的寫法:

update tableName
<dynamic prepend="set" >
......
<isNotNull prepend="," property="gmtModified" >
MODIFIED = #modified:TIMESTAMP#
</isNotNull>
</dynamic>
where ID = #id#

   正確的寫法(當然,這裏更推薦直接更新爲sysdate):

update tableName  set MODIFIED = #modified:TIMESTAMP#
<dynamic>
......
</dynamic>
where ID = #id#

2)不允許在where後添加1=1這樣的無用條件,where可以寫在prepend屬性裏,如:

   錯誤的寫法:

select count(*) from tableName t where 1=1
<dynamic>
......
</dynamic>

   正確的寫法:

select count(*) from tableName t
<dynamic prepend="where" >
......
</dynamic>

3)對大表進行查詢時,在SQLMAP中需要加上對空條件的判斷語句,如:

   性能上不保險的寫法:

select count(*) from tableName a
<dynamic prepend="where" >
<isNotEmpty prepend="AND" property="id" >
a.id = #id:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="email" >
a.email = #email:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="type" >
a.type = #type:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="no" >
a.no = #no:varchar#
</isNotEmpty>
</dynamic>

   性能上較保險的寫法(防止那些能保證查詢性能的關鍵條件都爲空):

select count(*) from tableName a
<dynamic prepend="where" >
<isNotEmpty prepend="AND" property="id" >
a.id = #id:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="email" >
a.email = #email:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="type" >
a.type = #type:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="no" >
a.no = #no:varchar#
</isNotEmpty>
<isEmpty property="id" >
<isEmpty property="email" >
<isEmpty property="no" >
query not allowed
</isEmpty>
</isEmpty>
</isEmpty>
</dynamic>

8.聚合函數常見問題

1)不要使用count(1)代替count(*)
2)count(column_name)計算該列不爲NULL的記錄條數
3)count(distinct column_name)計算該列不爲NULL的不重複值數量
4)count()函數不會返回NULL,但sum()函數可能返回NULL,可以使用nvl(sum(qty),0)來避免返回NULL

9. NULL的使用

1)理解NULL的含義,是"不確定",而不是"空"
2)查詢時,使用is null或者is not null
3)更新時,使用等於號,如:update tablename set column_name = null

10.STR2NUMLIST、STR2VARLIST函數的使用

1)適用情況:使用唯一值(或者接近唯一值)批量取數據時 ,能夠大大減少和數據庫的交互次數

2)編寫規範:a表必須放在from list的第一位,並且必須在select後加上下面的hint
  注意一:參數是由各個交易號拼成的字符串,以逗號間隔,不要有空格
  注意二:函數的參數是一個字符串,長度不能超過4000個字節
  注意三:函數生成的表只有一個字段,名字是column_value
  注意四:生成表的column_value字段是varchar2類型,如果希望是number類型,請使用str2numlist函數,並將vartabletype替換爲numtabletype

   錯誤的寫法(缺少hint):

select a.column_value, b.goods_title
from TABLE(CAST(str2varlist(:1) as vartabletype)) a, tableName b
where a.column_value = b.no;

   錯誤的寫法(函數生成的表必須放在from list的第一位):

select /*+ ordered use_nl(a,b) */ a.column_value, b.goods_title
from tableName b, TABLE(CAST(str2varlist(:1) as vartabletype)) a
where a.column_value = b.no;

   正確的寫法:

select /*+ ordered use_nl(a,b) */ a.column_value, b.goods_title
from TABLE(CAST(str2varlist(:1) as vartabletype)) a, tableName b
where a.column_value = b.no;

   如果要求返回的結果記錄條數和參數中交易號的個數一致,可使用外連接:

select /*+ ordered use_nl(a,b) */ a.column_value, b.goods_title
from TABLE(CAST(str2varlist(:1) as vartabletype)) a, tableName b
where a.column_value = b.no (+);

   如果參數內的值不唯一,可能返回多行,而並不需要返回多行,可考慮使用聚合函數:

select /*+ ordered use_nl(a,b) */
a.column_value, min(b.goods_title) goods_title
from TABLE(CAST(str2varlist(:1) as vartabletype)) a, tableName b
where a.column_value = b.no (+)
group by a.column_value;

   使用該函數與使用IN的區別:
    (1)IN返回的結果是無序的,而該函數返回的結果是以參數中各值的順序爲順序的
    (2)IN返回的結果是不重複的,而該函數返回的結果可重複,取決於輸入的參數

11.分頁查詢的使用

 

1 )分頁通常是先執行COUNT語句然後執行分頁語句,當COUNT返回值爲0的時候,應當避免執行後面的分頁語句

2 )有時,只須執行分頁語句而無須執行COUNT語句,就不要執行COUNT語句,例如,用戶下載excel格式的賬戶明細

3 )有時,在分頁前除了要統計COUNT還需要統計SUM,這些WHERE子句一致的統計應該在一條SQL中查出,而不是分多次統計

4)包含排序邏輯的分頁查詢寫法,必須是三層select嵌套:

   錯誤的寫法:

SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM tableName t
WHERE no = :1
AND gmt_create >= TO_DATE (:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE (:3, 'yyyy-mm-dd')
ORDER BY create DESC) t1
WHERE rnum >= :4 AND rnum < :5

   正確的寫法:

SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT t.*
FROM tableName t
WHERE no = :1
AND create >= TO_DATE (:2, 'yyyy-mm-dd')
AND create < TO_DATE (:3, 'yyyy-mm-dd')
ORDER BY create DESC) t1
WHERE ROWNUM <= :4) t2
WHERE rnum >= :5

5)不包含排序邏輯的分頁查詢寫法,則是兩層select嵌套,但對rownum的範圍指定仍然必須在不同的查詢層次指定:

   錯誤的寫法:

SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM tableName t
WHERE no = :1
AND create >= TO_DATE (:2, 'yyyy-mm-dd')
AND create < TO_DATE (:3, 'yyyy-mm-dd')) t1
WHERE rnum >= :4 AND rnum <= :5

   正確的寫法:

SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM tableName t
WHERE seller_account = :1
AND create >= TO_DATE (:2, 'yyyy-mm-dd')
AND create < TO_DATE (:3, 'yyyy-mm-dd')
AND ROWNUM <= :4) t1
WHERE rnum >= :5

6)注意下面兩種寫法的邏輯含義是不同的:

   按交易創建時間排序(倒序),然後再取前10條:

SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT t.*
FROM tableName t
WHERE no = :1
AND create >= TO_DATE (:2, 'yyyy-mm-dd')
AND create < TO_DATE (:3, 'yyyy-mm-dd')
ORDER BY create DESC) t1
WHERE ROWNUM <= 10) t2
WHERE rnum >= 1

   隨機取10條,然後在這10條中按照交易創建時間排序(倒序):

SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM tableName t
WHERE no = :1
AND create >= TO_DATE (:2, 'yyyy-mm-dd')
AND create < TO_DATE (:3, 'yyyy-mm-dd')
AND ROWNUM <= 10
ORDER BY create DESC) t1
WHERE rnum >= 1

7)先連接後分頁與先分頁後連接

   性能較差:

SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT a.*, b.fee
FROM tableName1 a, tableName2 b
WHERE a.no = b.no(+)
AND a.seller = :1
AND a.create >= TO_DATE (:2, 'yyyy-mm-dd')
AND a.create < TO_DATE (:3, 'yyyy-mm-dd')
ORDER BY a.create DESC) t1
WHERE ROWNUM <= :4) t2
WHERE rnum >= :5

   性能較好:

SELECT /*+ ordered use_nl(a,b) */
a.*, b.fee
FROM (SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT t.*
FROM tableName t
WHERE no = :1
AND create >= TO_DATE (:2, 'yyyy-mm-dd')
AND create < TO_DATE (:3, 'yyyy-mm-dd')
ORDER BY create DESC) t1
WHERE ROWNUM <= :4) t2
WHERE rnum >= :5) a,
tableName1 b
WHERE a.no = b.no(+)

   後面這種寫法的適用情況:

   a、where子句中的查詢條件都是針對 tableName 表的(否則得到的結果將不相同)
   b、關聯 tableName1 表時,用的是該表的主鍵或者唯一鍵字段(否則將改變結果集的條數)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章