IF NOT EXISTS (SELECT * FROM dbo.sysobjects
WHERE id = OBJECT_ID(N'[dbo].[Pr_QueryByPage]') AND OBJECTPROPERTY(id,N'IsProcedure') = 1)
BEGIN
EXEC dbo.sp_executesql @statement = N'
CREATE PROCEDURE [dbo].[Pr_QueryByPage]
@sqlstr NVARCHAR(4000), -- 查詢 SQL (不含 ORDER BY)
@currentpage INT, -- 第幾頁 (1-based)
@pagesize INT -- 每頁筆數
AS
BEGIN
SET NOCOUNT ON;
DECLARE @P1 INT, @rowcount INT;
-- 開啟遊標 (FORWARD_ONLY, READ_ONLY)
EXEC sp_cursoropen @P1 OUTPUT, @sqlstr, @scrollopt=1, @ccopt=1, @rowcount=@rowcount OUTPUT;
-- 返回總頁數、總筆數
SELECT CEILING(1.0 * @rowcount / @pagesize) AS TotalPage, @rowcount AS RowCount;
-- 計算游標提取起始位置
SET @currentpage = (@currentpage - 1) * @pagesize + 1;
-- 提取指定範圍
EXEC sp_cursorfetch @P1, 16, @currentpage, @pagesize;
-- 關閉遊標
EXEC sp_cursorclose @P1;
SET NOCOUNT OFF;
END';
END
GO
⚠️ **效能提示**:遊標法需建立臨時結果集,**不如 `TOP` 配合鍵值法效能好**,但可直接返回總筆數,免去第二次 `COUNT(*)` 查詢。
呼叫 `Pr_QueryByPage` 返回 **三個 ResultSet**:
CallableStatement cs = conn.prepareCall("exec Pr_QueryByPage 'SELECT * FROM ckdmzd', 1, 10");
boolean hasResultSet = cs.execute(); // true = 有 ResultSet
if (hasResultSet) {
ResultSet rs = cs.getResultSet(); // 第 1 個:空結構
// 第 2 個:分頁資訊
if (cs.getMoreResults()) {
rs = cs.getResultSet();
while (rs.next()) {
String pageCount = rs.getString(1);
String recordCount = rs.getString(2);
// 解析總頁數、總筆數
}
}
// 第 3 個:分頁資料
if (cs.getMoreResults()) {
rs = cs.getResultSet();
while (rs.next()) {
// 處理分頁資料列
}
}
}
此模式適用於 `PreparedStatement`、`CallableStatement`、`Statement` 所有 JDBC 執行介面。
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 系統遊標存儲過程 (本文) | 單次呼叫得總筆數+分頁資料、封裝邏輯 | 遊標開銷大、深分頁慢、佔用 tempdb | SQL 2000 既有系統、需總筆數、併發中低 |
| 雙查詢法 (COUNT + TOP 鍵值) | 效能佳、標準 SQL | 兩次往返、需應用層組裝 | 新開發、高併發、深分頁 |
| 臨時表/表變數分頁 | 邏輯直觀 | 寫入 tempdb、鎖爭用 | 複雜條件、一次性報表 |
升級至 SQL 2005 後,**首選 `ROW_NUMBER()` CTE 或 `OFFSET/FETCH` (2012+)**,棄用遊標分頁。JDBC 驅動同步升級為 `sqljdbc4.jar` (JDBC 4.0),支援 `getGeneratedKeys()`、表值參數等現代特性。
SQL Server 2000 JDBC 分頁,系統遊標存儲過程 + `CallableStatement` 多 `ResultSet` 處理是經典解法。理解 `sp_cursoropen/fetch/close` 機制與 JDBC 多結果集 API,可維護舊系統並平滑遷移至現代分頁模式。