SQL Server 無 LIMIT 子句,傳統分頁需用 TOP + NOT IN 或 ROW_NUMBER() 窗口函數。本文提供兩種經過實測的分頁寫法、完整可執行範例程式碼,並封裝為通用分頁工具類,支援自定義每頁筆數、頁碼傳遞、上一頁/下一頁/首尾頁導航。
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$pageSize = 30;
$offset = ($page - 1) * $pageSize;
// 總筆數
$total = (int) mssql_result(mssql_query("SELECT COUNT(1) FROM reguser"), 0);
$pageCount = ceil($total / $pageSize);
// 分頁查詢
$sql = "SELECT TOP $pageSize * FROM reguser
WHERE id NOT IN (
SELECT TOP $offset id FROM reguser ORDER BY id DESC
)
ORDER BY id DESC";
原理:子查詢先取出前 $offset 筆 ID,主查詢排除這些 ID 再取 TOP $pageSize。缺點:子查詢隨頁碼增大效能下降,不適合百萬級資料深分頁。
$sql = "SELECT * FROM (
SELECT ROW_NUMBER() OVER (ORDER BY id DESC) AS rn, *
FROM reguser
) AS t
WHERE rn BETWEEN " . ($offset + 1) . " AND " . ($offset + $pageSize);
優勢:單次掃描、索引友善、深分頁效能穩定。若需相容 SQL Server 2000,可改用 IDENTITY 暫存表法。
class Paginator {
private $pdo;
private $pageSize;
private $totalRows;
private $currentPage;
private $maxPage;
public function __construct(PDO $pdo, $pageSize = 20) {
$this->pdo = $pdo;
$this->pageSize = $pageSize;
}
public function setTotalRows($countSql, $params = []) {
$stmt = $this->pdo->prepare($countSql);
$stmt->execute($params);
$this->totalRows = (int) $stmt->fetchColumn();
$this->maxPage = max(1, (int) ceil($this->totalRows / $this->pageSize));
return $this;
}
public function setCurrentPage($page) {
$this->currentPage = max(1, min((int)$page, $this->maxPage));
return $this;
}
public function fetchPage($dataSql, $params = []) {
$offset = ($this->currentPage - 1) * $this->pageSize;
// 不同資料庫分頁語法不同,這裡以 MSSQL ROW_NUMBER 為例
$pagedSql = "SELECT * FROM ($dataSql) AS t WHERE rn BETWEEN " . ($offset + 1) . " AND " . ($offset + $this->pageSize);
$stmt = $this->pdo->prepare($pagedSql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function getNavHtml($baseUrl = '?') {
$html = "第 {$this->currentPage}/{$this->maxPage} 頁,共 {$this->totalRows} 筆 ";
if ($this->currentPage > 1) $html .= "首頁 上一頁 ";
if ($this->currentPage < $this->maxPage) $html .= "下一頁 尾頁";
return $html;
}
}
$pdo = new PDO("sqlsrv:Server=MYSQL2005;Database=edu_dzk", 'sa', '123456');
$pager = new Paginator($pdo, 10);
$pager->setTotalRows("SELECT COUNT(1) FROM reguser")
->setCurrentPage($_GET['page'] ?? 1);
$data = $pager->fetchPage("
SELECT ROW_NUMBER() OVER (ORDER BY id DESC) AS rn, *
FROM reguser
");
echo $pager->getNavHtml();
ORDER BY 欄位加索引,避免 Sort 運算子。SELECT *,減少 I/O 與網路傳輸。WHERE id > $lastId ORDER BY id LIMIT $pageSize,效能 O(1)。COUNT(*) 結果。MSSQL 分頁核心在於 ROW_NUMBER() OVER (ORDER BY ...) 窗口函數,配合 BETWEEN 取區間。封裝通用 Paginator 類別可屏蔽資料庫差異,專案中統一呼叫,提升開發效率與程式碼可維護性。