Retrieving the Number of Rows in a Result Set

Một phần của tài liệu Tài liệu Learning PHP Data Objects ppt (Trang 106 - 115)

In this chapter, we will examine:

How to retrieve the number of rows in the result sets returned by PDO How to fetch results starting at a specified row number

Retrieving the Number of Rows in a Result Set

As we have already discussed in Chapter 2, the PDOStatement::rowCount() method does not return the correct number of rows in a query. (It returns zero for both MySQL and SQLite.) The reason for such behavior is that the database management systems do not actually know this number until the last row of the query has been returned. The reason for the mysql_num_rows() function (and similar functions for other databases) returns the row count is that it preloads the whole result set into memory when you issue the query.

While it may seem convenient, this behavior is not recommended. If the query returns 20 rows, then the script can afford the memory usage. But what if the query returns several hundred thousands rows? They will all be kept in memory so that, on high traffic sites, the server may run out of resources.

The only logical measure (and the only option available with PDO) is to instruct the database to count the number of rows itself. No matter how complicated the query is, it can be rewritten to use the SQL COUNT() function to return just the number of rows that will satisfy the main query.

Let's take a look at the queries used in our application. (We will only examine the queries that return multiple rows.)

In books.php we have a query that joins two tables to present the list of books along with their authors :

SELECT authors.id AS authorId, firstName, lastName, books.*

FROM authors, books WHERE author=authors.id ORDER BY title;

To get the number of rows that this query returns we should rewrite it to look like the following:

SELECT COUNT(*) FROM authors, books WHERE author=authors.id;(*) FROM authors, books WHERE author=authors.id;

Note that we don't need the ORDER BY clause here as the order does not really matter for the count of rows.

In authors.php we simply select all the authors ordered by their last name and then their first name: then their first name:

SELECT * FROM authors ORDER BY lastName, firstName; FROM authors ORDER BY lastName, firstName;

This simply rewrites to the following:

SELECT COUNT(*) FROM authors;(*) FROM authors;

Another query that returns multiple rows is in author.php—it retrieves all the books written by a particular author:

SELECT * FROM books WHERE author=$id ORDER BY title;=$id ORDER BY title;

This translates to the following:

SELECT COUNT(*) FROM books WHERE author=$id; books WHERE author=$id;

As you can see, we rewrote all these queries in a similar way—by replacing the list of columns with COUNT(*) and trimming the ORDERBY clause. With this in mind, we can create a function that will accept a string containing the SQL to be executed and return the number of rows that the query will return. This function will have to perform these simple transformations:

Replace everything between SELECT and FROM with COUNT(*) in the passed string.

Remove ORDERBY and all the text after it.

The best way to achieve this transformation is to use regular expressions. As in previous chapters, we will use the PCRE extension. We will put the function into common.inc.php as we will call it from various places:

/**

* This function will return the number of rows a query will return * @param string $sql the SQL query

* @return int the number of rows the query specified will return * @throws PDOException if the query cannot be executed

*/

function getRowCount($sql) {

global $conn;

$sql = trim($sql);

$sql = preg_replace('~^SELECT\s.*\sFROM~s', 'SELECT COUNT(*) FROM', $sql);

$sql = preg_replace('~ORDER\s+BY.*?$~sD', '', $sql);

$stmt = $conn->query($sql);

$r = $stmt->fetchColumn(0);

$stmt->closeCursor();

return $r;

}

Let's run over the function to see what it does:

1. It imports the PDO connection object ($conn) into the local function scope.

2. It trims the possible spaces from the beginning and the end of the SQL query.

3. Two calls to preg_replace() do the main task of transforming the query.

Note how we use the pattern modifiers—the s modifier instructs PCRE to match newline characters with the dot, and the D modifier forces the $ to match the end of the whole string (not just before the first newline). We use these modifiers to make to make sure that the function will work properly with multiline queries.

We will now modify the three scripts to display the number of rows in each table that they return. Let's start with books.php:

<?php

/**

* This page lists all the books we have * PDO Library Management example application

* @author Dennis Popel */

// Don't forget the include include('common.inc.php');

// Display the header showHeader('Books');

// Get the count of books and issue the query

$sql = "SELECT authors.id AS authorId, firstName, lastName, books.*

FROM authors, books WHERE author=authors.id ORDER BY title";

$totalBooks = getRowCount($sql);

$q = $conn->query($sql);

$q->setFetchMode(PDO::FETCH_ASSOC);

// now create the table

?>

Total books: <?=$totalBooks?>

<table width="100%" border="1" cellpadding="3">

<tr style="font-weight: bold">

<td>Cover</td>

<td>Author and Title</td>

<td>ISBN</td>

<td>Publisher</td>

<td>Year</td>

<td>Summary</td>

<td>Edit</td>

</tr>

<?php

// Now iterate over every row and display it while($r = $q->fetch())

{ ?>

<tr>

<td>

<?php if($r['coverMime']) { ?>

<img src="showCover.php?book=<?=$r['id']?>">

<?php } else { ?>

n/a <? } ?>

</td>

<td>

<a href="author.php?id=<?=$r['authorId']?>"><?=htmlspecialchars ("$r[firstName] $r[lastName]")?></a><br/>

<b><?=htmlspecialchars($r['title'])?></b>

</td>

<td><?=htmlspecialchars($r['isbn'])?></td>

<td><?=htmlspecialchars($r['publisher'])?></td>

<td><?=htmlspecialchars($r['year'])?></td>

<td><?=htmlspecialchars($r['summary'])?></td>

<td>

<a href="editBook.php?book=<?=$r['id']?>">Edit</a>

</td>

</tr>

<?php }

?>

</table>

<a href="editBook.php">Add book...</a>

<?php

// Display footer showFooter();

As you can see, the modifications are pretty straightforward—we use the $sql variable to hold the query and pass it to both the getRowCount() function and the

$conn->query() method. We also display a message above the table, which tells us how many books there are in the database.

Now if you refresh the books.php page, you will see the following:

The changes to authors.php are similar:

<?php /**

* This page lists all the authors we have * PDO Library Management example application * @author Dennis Popel

*/

// Don't forget the include include('common.inc.php');

// Display the header showHeader('Authors');

// Get the number of authors and issue the query

$sql = "SELECT * FROM authors ORDER BY lastName, firstName";

$totalAuthors = getRowCount($sql);

$q = $conn->query($sql);

// now create the table

?>

Total authors: <?=$totalAuthors?>

<table width="100%" border="1" cellpadding="3">

<tr style="font-weight: bold">

<td>First Name</td>

<td>Last Name</td>

<td>Bio</td>

<td>Edit</td>

</tr>

<?php

// Now iterate over every row and display it while($r = $q->fetch(PDO::FETCH_ASSOC)) {

?>

<tr>

<td><?=htmlspecialchars($r['firstName'])?></td>

<td><?=htmlspecialchars($r['lastName'])?></td>

<td><?=htmlspecialchars($r['bio'])?></td>

<td>

<a href="editAuthor.php?author=<?=$r['id']?>">Edit</a>

</td>

</tr>

<?php }

?>

</table>

<a href="editAuthor.php">Add Author...</a>

<?php

// Display footer showFooter();

The authors.php now should display the following:

Finally, author.php will look like this::

<?php /**

* This page shows an author's profile

* PDO Library Management example application * @author Dennis Popel

*/

// Don't forget the include include('common.inc.php');

// Get the author

$id = (int)$_REQUEST['id'];

$q = $conn->query("SELECT * FROM authors WHERE id=$id");

$author = $q->fetch(PDO::FETCH_ASSOC);

$q->closeCursor();

$q = null;

// Now see if the author is valid - if it's not, // we have an invalid ID

if(!$author) {

showHeader('Error');

echo "Invalid Author ID supplied";

showFooter();

exit;

}

// Display the header - we have no error

showHeader("Author: $author[firstName] $author[lastName]");

// Now get the number and fetch all the books

$sql = "SELECT * FROM books WHERE author=$id ORDER BY title";

$totalBooks = getRowCount($sql);

$q = $conn->query($sql);

$q->setFetchMode(PDO::FETCH_ASSOC);

// now display everything

?>

<h2>Author</h2>

<table width="60%" border="1" cellpadding="3">

<tr>

<td><b>First Name</b></td>

<td><?=htmlspecialchars($author['firstName'])?></td>

</tr>

<tr>

<td><b>Last Name</b></td>

<td><?=htmlspecialchars($author['lastName'])?></td>

</tr>

<tr>

<td><b>Bio</b></td>

<td><?=htmlspecialchars($author['bio'])?></td>

</tr>

<tr>

<td><b>Total books</td>

<td><?=$totalBooks?></td>

</tr>

</table>

<a href="editAuthor.php?author=<?=$author['id']?>">Edit author...</a>

<h2>Books</h2>

<table width="100%" border="1" cellpadding="3">

<tr style="font-weight: bold">

<td>Title</td>

<td>ISBN</td>

<td>Publisher</td>

<td>Year</td>

<td>Summary</td>

</tr>

<?php

// Now iterate over every book and display it while($r = $q->fetch()) {

?>

<tr>

<td><?=htmlspecialchars($r['title'])?></td>

<td><?=htmlspecialchars($r['isbn'])?></td>

<td><?=htmlspecialchars($r['publisher'])?></td>

<td><?=htmlspecialchars($r['year'])?></td>

<td><?=htmlspecialchars($r['summary'])?></td>

</tr>

<?php }

?>

</table>

<?php

// Display footer showFooter();

The output should look like this. (I scrolled the page down a bit to save space):

You should switch between MySQL and SQLite in common.inc.php to make sure both databases work.

This approach may work for many cases, but is not suitable for all queries. One such example is a query that uses a GROUPBY clause. If you rewrite such query with the getRowCount() function you will get incorrect results as the grouping will be applied and the query will return several rows. (The number of rows will be equal to the number of distinct values in the column you are grouping by.)

Một phần của tài liệu Tài liệu Learning PHP Data Objects ppt (Trang 106 - 115)

Tải bản đầy đủ (PDF)

(188 trang)