實務上常遇到「查詢條件不固定、需分頁」的需求。本文展示一個完整預存程序 PROC_CLIENT_LIST:10 個可選查詢條件 (字串 LIKE、日期範圍、數值等值)、ROWNUM 雙層分頁、綁定變數防注入、單次往返回傳游標。
CREATE OR REPLACE PROCEDURE PROC_CLIENT_LIST (
P_CURSOR OUT SYS_REFCURSOR, -- 結果集游標
P_CHARACTERS IN VARCHAR2, -- 客戶性質
P_STATES IN VARCHAR2, -- 客戶狀態
P_TYPE IN VARCHAR2, -- 客戶類型
P_CALLING IN VARCHAR2, -- 客戶行業
P_NAME IN VARCHAR2, -- 客戶名稱
P_BEGINTIME IN DATE, -- 建立日期起
P_ENDTIME IN DATE, -- 建立日期迄
P_AREA IN NUMBER, -- 客戶地區
P_SOURCE IN VARCHAR2, -- 客戶來源
P_IMPORTENT IN VARCHAR2, -- 重要程度
P_START_ROW IN NUMBER, -- 起始列 (1-based)
P_END_ROW IN NUMBER -- 結束列
) IS
V_SQL_STR VARCHAR2(3000);
BEGIN
-- 動態組裝 SQL:三層 ROWNUM 分頁 + 所有條件用 :param IS NULL OR 寫法
V_SQL_STR :=
'SELECT * FROM (
SELECT ROW_.*, ROWNUM ROWNUM_ FROM (
SELECT * FROM CLIENTINFO C
WHERE (:CHARACTERS IS NULL OR C.CHARACTERS LIKE :CHARACTERS_LIKE)
AND (:STATES IS NULL OR C.STATES LIKE :STATES_LIKE)
AND (:TYPE IS NULL OR C.TYPE LIKE :TYPE_LIKE)
AND (:CALLING IS NULL OR C.CALLING LIKE :CALLING_LIKE)
AND (:NAME IS NULL OR C.NAME LIKE :NAME_LIKE)
AND (:BEGINTIME IS NULL OR C.CREATETIME > :BEGINTIME)
AND (:ENDTIME IS NULL OR C.CREATETIME < :ENDTIME)
AND (:AREA IS NULL OR C.AREA = :AREA)
AND (:SOURCE IS NULL OR C.CLIENTSOURCE LIKE :SOURCE_LIKE)
AND (:IMPORTENT IS NULL OR C.IMPORTENT LIKE :IMPORTENT_LIKE)
) ROW_
WHERE ROWNUM <= :END_ROW
) WHERE ROWNUM_ > :START_ROW';
-- 開啟游標,USING 子句按佔位符順序綁定 (每個 LIKE 條件需傳兩次:原值 + 加 % 值)
OPEN P_CURSOR FOR V_SQL_STR USING
P_CHARACTERS, '%' || P_CHARACTERS || '%',
P_STATES, '%' || P_STATES || '%',
P_TYPE, '%' || P_TYPE || '%',
P_CALLING, '%' || P_CALLING || '%',
P_NAME, '%' || P_NAME || '%',
P_BEGINTIME, P_BEGINTIME,
P_ENDTIME, P_ENDTIME,
P_AREA, P_AREA,
P_SOURCE, '%' || P_SOURCE || '%',
P_IMPORTENT, '%' || P_IMPORTENT || '%',
P_END_ROW, P_START_ROW;
END PROC_CLIENT_LIST;
/
| 技巧 | 實作方式 | 優勢 |
|---|---|---|
| 動態條件 | :PARAM IS NULL OR COL LIKE :PARAM_LIKE | 單一 SQL 覆蓋所有組合,避免動態拼接 SQL 注入風險 |
| LIKE 參數雙重綁定 | USING 子句傳 原值, '%'||原值||'%' | 保持綁定變數優勢,優化器可重用計畫 |
| 日期範圍 | :DT IS NULL OR COL > :DT | 支援單邊/雙邊/無日期查詢 |
| 分頁邏輯 | 三層巢狀:排序/過濾 → ROWNUM <= END → ROWNUM_ > START | Oracle 10g/11g 相容,無需 OFFSET/FETCH |
| 綁定變數順序 | 嚴格對應 SQL 中出現順序 | 避免 ORA-01006/01008 綁定錯誤 |
// MyBatis Mapper
@Select("{call PROC_CLIENT_LIST(#{cursor, mode=OUT, jdbcType=ORACLE_CURSOR, resultMap=ClientMap}, " +
"#{characters}, #{states}, #{type}, #{calling}, #{name}, " +
"#{beginTime}, #{endTime}, #{area}, #{source}, #{importent}, " +
"#{startRow}, #{endRow})}")
List queryClients(@Param("characters") String characters, ...);
// 或 XML
IDX_CLIENT_QUERY (CHARACTERS, STATES, CREATETIME) 等ROWID 分頁或延遲關聯 (先查 PK 再 JOIN 明細)DBMS_STATS.GATHER_TABLE_STATS('SCHEMA','CLIENTINFO')
-- 12c 起支援 OFFSET ... FETCH,程式碼更簡潔
OPEN P_CURSOR FOR
SELECT * FROM CLIENTINFO C
WHERE (:CHARACTERS IS NULL OR C.CHARACTERS LIKE :CHARACTERS_LIKE)
...
ORDER BY C.CREATETIME DESC
OFFSET :START_ROW ROWS FETCH NEXT :PAGESIZE ROWS ONLY;
但 10g/11g 仍廣泛使用,ROWNUM 三層寫法為相容性最佳方案。