分頁是 Web 應用處理大量資料的核心技術。本文從 SQL LIMIT 原理出發,逐步建構程式碼,最終封裝為可複用的 Pager 類別,支援多資料庫相容。
MySQL 分頁語法:SELECT * FROM table LIMIT offset, rows
LIMIT 0, 10LIMIT 10, 10LIMIT (N-1)*PageSize, PageSize衍生參數:總筆數(COUNT(*))、總頁數、上一頁、下一頁、首頁、尾頁。
<?php
$link = mysqli_connect('localhost', 'user', 'pass', 'db');
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$pageSize = 10;
// 取得總筆數
$total = (int)mysqli_fetch_row(mysqli_query($link, 'SELECT COUNT(*) FROM `table`'))[0];
$totalPages = $total ? ceil($total / $pageSize) : 0;
$page = min($page, max(1, $totalPages));
// 產生導覽列
$nav = '';
if ($page > 1) {
$nav .= '<a href="?page=1">首頁</a> | ';
$nav .= '<a href="?page=' . ($page-1) . '">上一頁</a> | ';
}
if ($page < $totalPages) {
$nav .= '<a href="?page=' . ($page+1) . '">下一頁</a> | ';
$nav .= '<a href="?page=' . $totalPages . '">尾頁</a>';
}
// 取得當頁資料
$offset = ($page - 1) * $pageSize;
$sql = "SELECT * FROM `table` ORDER BY id DESC LIMIT {$offset}, {$pageSize}";
$result = mysqli_query($link, $sql);
$data = mysqli_fetch_all($result, MYSQLI_ASSOC);
// 顯示
foreach ($data as $row) { /* 輸出 */ }
echo $nav;
?>
<?php
// Pager.class.php
class Pager {
public $PageSize, $CurrentPageID, $NextPageID, $PreviousPageID;
public $numPages, $numItems, $isFirstPage, $isLastPage, $sql;
public function __construct($options) {
global $db; // Pear DB 連線物件
foreach (['PageSize','CurrentPageID','sql','numItems'] as $k) {
if (!empty($options[$k])) $this->$k = $options[$k];
}
$this->calculate();
}
private function calculate() {
if (!isset($this->numItems)) {
$res = $db->query($this->sql);
$this->numItems = $res->numRows();
}
$this->numPages = $this->numItems ? ceil($this->numItems / $this->PageSize) : 0;
$this->CurrentPageID = max(1, min($this->CurrentPageID, max(1, $this->numPages)));
$this->isFirstPage = ($this->CurrentPageID === 1);
$this->isLastPage = ($this->CurrentPageID === $this->numPages);
$this->PreviousPageID = $this->isFirstPage ? null : $this->CurrentPageID - 1;
$this->NextPageID = $this->isLastPage ? null : $this->CurrentPageID + 1;
}
// 取得資料庫結果物件(大量資料建議)
public function getDataLink() {
if (!$this->numItems) return false;
global $db;
$from = ($this->CurrentPageID - 1) * $this->PageSize;
return $db->limitQuery($this->sql, $from, $this->PageSize);
}
// 取得二維陣列結果(中小量資料)
public function getPageData() {
$res = $this->getDataLink();
if (!$res) return false;
$data = [];
while ($row = $res->fetchRow()) $data[] = $row;
return $data;
}
}
?>
<?php
require 'Pager.class.php';
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$sql = 'SELECT * FROM members ORDER BY id';
$pager = new Pager(['sql'=>$sql, 'PageSize'=>10, 'CurrentPageID'=>$page]);
$data = $pager->getPageData();
// 顯示導覽
$nav = '';
if ($pager->isFirstPage) $nav .= '首頁 | 上一頁 | ';
else $nav .= '<a href="?page=1">首頁</a> | <a href="?page='.$pager->PreviousPageID.'">上一頁</a> | ';
if ($pager->isLastPage) $nav .= '下一頁 | 尾頁';
else $nav .= '<a href="?page='.$pager->NextPageID.'">下一頁</a> | <a href="?page='.$pager->numPages.'">尾頁</a>';
echo $nav;
foreach ($data as $row) { /* 輸出會員資料 */ }
?>
不同資料庫分頁語法不同:
LIMIT offset, countLIMIT count OFFSET offsetOFFSET offset ROWS FETCH NEXT count ROWS ONLYROWNUM 子查詢使用 Pear DB::limitQuery() 或 PDO 搭配抽象層可自動處理差異。
class MemberPager extends Pager {
public function showList() {
$data = $this->getPageData();
// 自訂 HTML 輸出邏輯
}
}
$pager = new MemberPager($options);
$pager->showList();