在 Web 應用開發中,分頁查詢是核心且必須解決的效能議題。本文將詳細示範如何在 ASP.NET 環境下,透過呼叫 Oracle 預存程序 實現高效率的分頁邏輯,並提供完整的 PL/SQL 包定義、包主體實作與 C# 呼叫代碼供參考。
CREATE OR REPLACE PACKAGE MaterialManage IS
TYPE T_CURSOR IS REF CURSOR;
PROCEDURE Per_QuickPage (
TbName IN VARCHAR2, -- 表名
FieldStr IN VARCHAR2, -- 欄位集合
RowFilter IN VARCHAR2, -- 過濾條件
SortStr IN VARCHAR2, -- 排序欄位
RownumFieldStr IN VARCHAR2, -- 分頁條件
TotalCount OUT NUMBER, -- 總筆數
Cur_ReturnCur OUT T_CURSOR -- 回傳游標
);
END MaterialManage;
CREATE OR REPLACE PACKAGE BODY MaterialManage IS
PROCEDURE Per_QuickPage (
TbName IN VARCHAR2,
FieldStr IN VARCHAR2,
RowFilter IN VARCHAR2,
SortStr IN VARCHAR2,
MinRowNum IN NUMBER, -- 分頁下限
MaxRowNum IN NUMBER, -- 分頁上限
TotalCount OUT NUMBER,
Cur_ReturnCur OUT T_CURSOR
) IS
v_SourceTb1 VARCHAR2(3000);
v_SourceTb2 VARCHAR2(3000);
v_SourceTb3 VARCHAR2(3000);
v_SourceTb4 VARCHAR2(3000);
v_TotalCount VARCHAR2(50);
v_sql VARCHAR2(3000);
BEGIN
-- 第 1 層:選取指定欄位
v_SourceTb1 := '(SELECT ' || FieldStr || ' FROM ' || TbName || ') SourceTb1';
-- 第 2 層:套用過濾與排序
v_SourceTb2 := '(SELECT * FROM ' || v_SourceTb1 || ' WHERE ' || RowFilter || ' ' || SortStr || ') SourceTb2';
-- 第 3 層:加上 ROWNUM 並截取上限
v_SourceTb3 := '(SELECT ROWNUM AS RowIndex, SourceTb2.* FROM ' || v_SourceTb2 || ' WHERE ROWNUM <= ' || MaxRowNum || ') SourceTb3';
-- 第 4 層:計算總筆數(不分頁)
v_SourceTb4 := '(SELECT * FROM ' || v_SourceTb1 || ' WHERE ' || RowFilter || ') SourceTb4';
v_sql := 'SELECT COUNT(*) AS TotalCount FROM ' || v_SourceTb4;
EXECUTE IMMEDIATE v_sql INTO v_TotalCount;
TotalCount := v_TotalCount;
-- 最後依 RowIndex 篩選分頁區間
v_sql := 'SELECT * FROM ' || v_SourceTb3 || ' WHERE RowIndex >= ' || MinRowNum;
OPEN Cur_ReturnCur FOR v_sql;
END Per_QuickPage;
END MaterialManage;
原理說明: Oracle 的 ROWNUM 偽欄位特性使得分頁成為可能。上述邏輯先查總筆數,再透過多層內聯視圖疊加 ROWNUM,最後在外層依 RowIndex 篩選目標頁面區間。回傳兩個參數:TotalCount(總筆數)與 Cur_ReturnCur(結果集游標)。
作者註:實測上述分頁效能並非最優,若有更佳寫法歡迎分享交流。
///
/// 呼叫預存程序實現快速分頁
///
public DataTable QuickPage(
string TbName, string FieldStr, string RowFilter, string SortStr,
int MinRowNum, int MaxRowNum, ref int RecordCount)
{
using (OracleConnection conn = new OracleConnection(
ConfigurationSettings.AppSettings["OracleConnstr"].ToString()))
using (OracleCommand cmd = new OracleCommand("MaterialManage.Per_QuickPage", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("TbName", OracleType.VarChar, 50).Value = TbName;
cmd.Parameters["TbName"].Direction = ParameterDirection.Input;
cmd.Parameters.Add("FieldStr", OracleType.VarChar, 3000).Value = FieldStr;
cmd.Parameters["FieldStr"].Direction = ParameterDirection.Input;
cmd.Parameters.Add("RowFilter", OracleType.VarChar, 3000).Value = RowFilter;
cmd.Parameters["RowFilter"].Direction = ParameterDirection.Input;
cmd.Parameters.Add("SortStr", OracleType.VarChar, 3000).Value = SortStr;
cmd.Parameters["SortStr"].Direction = ParameterDirection.Input;
cmd.Parameters.Add("MinRowNum", OracleType.Number).Value = MinRowNum;
cmd.Parameters["MinRowNum"].Direction = ParameterDirection.Input;
cmd.Parameters.Add("MaxRowNum", OracleType.Number).Value = MaxRowNum;
cmd.Parameters["MaxRowNum"].Direction = ParameterDirection.Input;
cmd.Parameters.Add("TotalCount", OracleType.Number).Direction = ParameterDirection.Output;
cmd.Parameters["TotalCount"].Value = 0;
cmd.Parameters.Add("Cur_ReturnCur", OracleType.Cursor).Direction = ParameterDirection.Output;
DataSet ds = new DataSet();
using (OracleDataAdapter adapter = new OracleDataAdapter(cmd))
{
adapter.Fill(ds);
}
RecordCount = int.Parse(cmd.Parameters["TotalCount"].Value.ToString());
return ds.Tables[0];
}
}
SortStr 欄位有索引,避免全表排序。ROW_NUMBER() OVER() 分析函數(Oracle 10g+),效能優於 ROWNUM 多層巢狀。using 區塊自動釋放,避免連線洩漏。