Thuật toán phân trang với PHP và MySQL

1. Tại sao phải phân trang bài viết. Như phần giới thiệu mình đã nói. Thay bằng việc bạn hiển thị tất các các records thì bạn chia số records đó thành nhiều trang. Và người dùng có thể click vào các trang đó để xem các records cũ hơn. Phân trang cũng sẽ giúp cho máy chủ xử lý CSDL không phải ...

1. Tại sao phải phân trang bài viết.

Như phần giới thiệu mình đã nói. Thay bằng việc bạn hiển thị tất các các records thì bạn chia số records đó thành nhiều trang. Và người dùng có thể click vào các trang đó để xem các records cũ hơn. Phân trang cũng sẽ giúp cho máy chủ xử lý CSDL không phải truy vấn một lúc quá nhiều dữ liệu.

Thành quả bạn nhận được sau khi thực hiện

Demo

 

2. Thuật toán phân trang bài viết.

Thực tế thì phân trang không khó như bạn nghĩ. Có những lập trình viên phải mất một khoảng thời gian khá dài mới hiểu và có thể tự xây dựng cho mình một class phân trang theo cách mà mình mong muốn. Thì nói một cách chung chung để có thể phân trang thì bạn cần xác định một số yêu tố sau.

  1. Tổng số records.
  2. Số records/1 trang.
  3. Tổng số trang dựa trên tổng số records và số records/1 trang
  4. Số trang hiện tại.

Để hiểu rõ hơn chúng ta sẽ xây dựng một giả thiết như sau: Bạn có 1000 records và yêu cầu bây giờ là hãy phân trang tổng số records đó và mỗi trang phải có 10 records.

Thì việc đầu tiên bạn sẽ xác định được hai trong bốn yếu tố bên trên bao gồm:

// Tổng số records
$totalRecords = 1000;
// Số records/1 trang
$recordPerPage = 10; 

Bây giờ bạn phải xác định được tổng số trang là bao nhiêu? Bạn sẽ tiến hành một phép chia dựa trên tổng số records và số records/1 trang.

$totalPage = $totalRecords/$recordPerpage;
// $totalPage = (1000/10); //100 trang, phép chia lấy phần nguyên

Nếu chúng ta có 1001 records. Trong khi nếu áp dụng phương pháp tính như trên thì tổng số trang mà bạn nhận được kết quả:

$totalPage = (1001/10) // 100 trang

Thực tế thì tổng số trang bạn mong muốn phải là 1001 trang. Để làm được điều này thì PHP cung cấp cho bạn một hàm ceil()

  • ceil(): Hàm làm tròn phép chia ở cận trên.
$totalPage = ceil($totalRecords/$recordPerPage); //101 trang

Bước tiếp theo bạn cần xác định trang hiện tại của bạn.

Để xác đinh được trang hiện tại của bạn giả sử mình có biến $currentPage. Thông thường mình sẽ nhận được giá trị của biến này thông qua tham số page trên địa chỉ URL(QUERY_STRING) của bạn. Mặc đinh $currentPage=1

$currentPage = isset($_GET['page']) && (int)$_GET['page'] >=1 ? (int) $_GET('page') : 1;

=> Tới đây thì bạn đã đủ cả bốn yếu tố rồi.

Nhưng vấn đề là làm thế nào để truy vấn lấy ra các records tương ứng với mỗi trang? Để giải quyết vấn đề này thì ta sử dụng LIMIT trong MySQL. Như vậy câu truy vấn cho từng trang sẽ như sau:

  • Trang 01: LIMIT 0, 10
  • Trang 02: LIMIT 10, 10
  • Trang 03: LIMIT 20, 10

Bây giờ là làm thế nào để xác định các con số màu đỏ ở các câu truy vấn trên? Các con số màu đỏ ta gọi là start hay là offset. OK chúng ta sẽ định nghĩa lại một chút.

  • $totalRecords: tổng số records
  • $currentPage: trang hiện tại
  • $recordPerPage: số records hiển thị trên mỗi trang
  • $offset: record bắt đầu trong câu lệnh SQL

Và đây là công thức tính start

  • Trang 01: $offset = (1 - 1) * 10 =  0, tương đương với ($currentPage- 1) * $recordPerPage.
  • Trang 02: $offset= (2 - 1) * 10 = 10, tương đương với ($currentPage- 1) * $recordPerPage.
  • Trang 03: $offset= (3 - 1) * 10 = 20, tương đương với ($currentPage - 1) * $recordPerPage.

Trong bài viết này mình sử dụng bảng employees trong CSDL mẫu.

CREATE TABLE `employees` (
  `employeeNumber` int(11) NOT NULL,
  `lastName` varchar(50) NOT NULL,
  `firstName` varchar(50) NOT NULL,
  `extension` varchar(10) NOT NULL,
  `email` varchar(100) NOT NULL,
  `officeCode` varchar(10) NOT NULL,
  `reportsTo` int(11) DEFAULT NULL,
  `jobTitle` varchar(50) NOT NULL,
  PRIMARY KEY (`employeeNumber`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Danh sách records trong bảng employees

SELECT * FROM employees

Try In Out

Tổng số bản ghi trong bảng employees.

SELECT count(*) as totalRecords FROM employees

Try In Out

Mọi thứ đã đầy đủ chúng ta bắt đầu vào code nào.

Chúng ta sẽ có hai phần.

  • Phần layout HTML.
  • Phần code PHP.

1. Layout HTML

<!DOCTYPE html>
<html>
    <head>
        <title>Phân trang trong PHP và MySQL</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <?php 
        // PHẦN XỬ LÝ PHP
        ?>
        <div>
            <?php 
            // PHẦN HIỂN THỊ DANH SÁCH
            ?>
        </div>
        <div class="pagination">
           <?php 
            // PHẦN HIỂN THỊ PHÂN TRANG
           ?>
        </div>
    </body>
</html>

P/s: Các bạn có thể thêm một chút CSS để cho nó được thêm hoa, thêm cành và thêm lá.

2. Phần code PHP

Phần này xử lý truy vấn CSDL và thuật toán phân trang, Nó khá quan trọng bởi nó tính toán các thông số phân trang và khởi tạo các biến được sử dụng trong tthuật toán.

define("DB_HOST", "localhost");
/** The name of the database for demo*/
define("DB_NAME", "demo_db");
/** MySQL database username */
define("DB_USER", "demo_db");
/** MySQL database password */
define("DB_PASS", "Gu5ZwYzL");

// Bước 1: Kết nối tới CSDL
$dbconn = mysql_connect(DB_HOST,DB_USER,DB_PASS) or die('Không thể kết nối tới CSDL');
mysql_select_db(DB_NAME, $dbconn) or die('Không thể kết nối tới CSDL!');

// Bước 2: Tính tổng $totalRecords
$query = "SELECT count(*) AS totalRecords FROM employees";
$result = mysql_query($query, $dbconn);
$row = mysql_fetch_assoc($result);
$totalRecords = !empty($row) ? $row['totalRecords'] : 0;

// Bước 3: Lấy $currentPage và thiết lập $recordPerPage
$currentPage = isset($_GET['page']) && (int) $_GET['page'] > 0 ? (int) $_GET['page'] : 1;
$recordPerPage = 5;

// Bước 4: Tính tổng số trang($totalPage) và tính $offset
$totalPage = ceil($totalRecords/$recordPerPage);
// Tính $offset
$offset = ($currentPage-1)*$recordPerPage;
$limit = "LIMIT $offset,$recordPerPage";

$query = "SELECT * FROM employees ".$limit; 
$result = mysql_query($query, $dbconn);

Phần hiển thị danh sách: Lặp qua các reocords để hiển thị danh sách.

<?php if(mysql_num_rows($result) > 0){ ?>
  <table class="tbl-grid" cellpadding="0" cellspacing="0" awidth="100%">
	<thead><tr>
	  <td class="gridheader">employeeNumber</td>
	  <td class="gridheader">lastName</td>
	  <td class="gridheader">firstName</td>
	  <td class="gridheader">extension</td>
	  <td class="gridheader">email</td>
	  <td class="gridheader">officeCode</td>
	  <td class="gridheader">reportsTo</td>
	  <td class="gridheader">jobTitle</td>
	  </tr></thead>
	<?php while ($row = mysql_fetch_assoc($result)){?>
	<tr>
	  <td><?php echo $row['employeeNumber'] ?></td>
	  <td><?php echo $row['lastName'] ?></td>
	  <td><?php echo $row['firstName'] ?></td>
	  <td><?php echo $row['extension'] ?></td>
	  <td><?php echo $row['email'] ?></td>
	  <td><?php echo $row['officeCode'] ?></td>
	  <td><?php echo $row['reportsTo'] ?></td>
	  <td><?php echo $row['jobTitle'] ?></td>
	</tr>
	<?php } ?>
  </table>
<?php } ?>

Phần hiển thị phân trang.

<div class="pagination">
  <?php
	// PHẦN HIỂN THỊ PHÂN TRANG

	// Button trang trước
	if($currentPage > 1 && $totalPage > 0){
	echo '<a href="index.php?page='.($currentPage-1).'&t='.time().'">&larr;</a>';
	}
	// Danh sách trang
	for($i=1; $i<=$totalPage; $i++){
	echo '<a'.($currentPage==$i?' class="current"':').' href="index.php?page='.$i.'&t='.time().'">'.$i.'</a>';
	}

	// Button trang kế tiếp
	if($currentPage < $totalPage && $totalPage > 1){
	echo '<a href="index.php?page='.($currentPage+1).'&t='.time().'">&rarr;</a>';
	}
  ?>
</div>

Và đây là code của tất cả thuật toán.

<?php
define("DB_HOST", "localhost");
/** The name of the database for demo */
define("DB_NAME", "demo_db");
/** MySQL database username */
define("DB_USER", "demo_db");
/** MySQL database password */
define("DB_PASS", "Gu5ZwYzL");

// Bước 1: Kết nối tới CSDL
$dbconn = mysql_connect(DB_HOST,DB_USER,DB_PASS) or die('Không thể kết nối tới CSDL');
mysql_select_db(DB_NAME, $dbconn) or die('Không thể kết nối tới CSDL!');

// Bước 2: Tính tổng $totalRecords
$query = "SELECT count(*) AS totalRecords FROM employees";
$result = mysql_query($query, $dbconn);
$row = mysql_fetch_assoc($result);
$totalRecords = !empty($row) ? $row['totalRecords'] : 0;

// Bước 3: Lấy $currentPage và thiết lập $recordPerPage
$currentPage = isset($_GET['page']) && (int) $_GET['page'] > 0 ? (int) $_GET['page'] : 1;
$recordPerPage = 5;

// Bước 4: Tính tổng số trang($totalPage) và tính $offset
$totalPage = ceil($totalRecords/$recordPerPage);
// Tính $offset
$offset = ($currentPage-1)*$recordPerPage;
$limit = "LIMIT $offset,$recordPerPage";

$query = "SELECT * FROM employees ".$limit; 
$result = mysql_query($query, $dbconn);

// PHẦN HIỂN THỊ DANH SÁCH
?>
<div class="container">
  <?php if(mysql_num_rows($result) > 0){ ?>
  <table class="tbl-grid" cellpadding="0" cellspacing="0" awidth="100%">
	<thead><tr>
	  <td class="gridheader">employeeNumber</td>
	  <td class="gridheader">lastName</td>
	  <td class="gridheader">firstName</td>
	  <td class="gridheader">extension</td>
	  <td class="gridheader">email</td>
	  <td class="gridheader">officeCode</td>
	  <td class="gridheader">reportsTo</td>
	  <td class="gridheader">jobTitle</td>
	  </tr></thead>
	<?php while ($row = mysql_fetch_assoc($result)){?>
	<tr>
	  <td><?php echo $row['employeeNumber'] ?></td>
	  <td><?php echo $row['lastName'] ?></td>
	  <td><?php echo $row['firstName'] ?></td>
	  <td><?php echo $row['extension'] ?></td>
	  <td><?php echo $row['email'] ?></td>
	  <td><?php echo $row['officeCode'] ?></td>
	  <td><?php echo $row['reportsTo'] ?></td>
	  <td><?php echo $row['jobTitle'] ?></td>
	</tr>
	<?php } ?>
  </table>
  <?php } ?>

  <br />
  <div class="pagination">
	<?php
// PHẦN HIỂN THỊ PHÂN TRANG

// Button trang trước
if($currentPage > 1 && $totalPage > 0){
echo '<a href="index.php?page='.($currentPage-1).'&t='.time().'">←</a>';
}
// Danh sách trang
for($i=1; $i<=$totalPage; $i++){
echo '<a'.($currentPage==$i?' class="current"':').' href="index.php?page='.$i.'&t='.time().'">'.$i.'</a>';
}

// Button trang kế tiếp
if($currentPage < $totalPage && $totalPage > 1){
echo '<a href="index.php?page='.($currentPage+1).'&t='.time().'">→</a>';
}
?>
  </div>
</div>

Tổng kết.

Qua vài viết này chắc các bạn đã hiểu và làm thế nào để phân trang trong PHP và MySQL rồi. Đúng ra mình không muốn share bài này vì kiểu phân trang này cơ bản và bạn có có thể dễ dàng tìm kiếm trên các trang mạng chia sẻ. Trong bài tiếp mình sẽ hướng dẫn các bạn phân trang nâng cao hơn đó là làm sao ta có thể giới hạn được số trang trên một webpage thay vì hiển thị tất cả số trang như trong bài viết này. Trước bản thân mình mới tập tành cũng bị vướng không làm sao có thể chia đoạn trong thuật toán phân trang. Thì bài viết [Nâng cao] Phần trang trong PHP và MySQL mình sẽ hướng dẫn các bạn làm được điều đó.

    0