热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

五、处理行集

五、处理行集现实生活中的动态、数据驱动的web应用程序彼此非常不同,因为它们的复杂性取决于它

五、处理行集

现实生活中的动态、数据驱动的 web 应用程序彼此非常不同,因为它们的复杂性取决于它们所服务的目的。然而,它们几乎都有一些共同的特点。这些特性之一是能够对长结果列表进行分页,以便于使用和加快页面加载时间。

正确的分页需要计算从数据库返回的总行数、页面大小(这是一个可配置的选项)和当前页面的数量。基于此数据,很容易将起始偏移量计算到结果集中,以仅显示行的子集。

在本章中,我们将研究:


  • 如何检索 PDO 返回的结果集中的行数

  • 如何从指定的行号开始获取结果


检索结果集中的行数

正如我们在第 2 章中所讨论的, PDOStatement::rowCount()方法不会在查询中返回正确的行数。(MySQL 和 SQLite 都返回零。)这种行为的原因是数据库管理系统在返回查询的最后一行之前实际上不知道这个数字。 mysql_num_rows()函数(以及其他数据库的类似函数)返回行计数的原因是,当您发出查询时,它会将整个结果集预加载到内存中。

虽然看起来很方便,但不建议使用此行为。如果查询返回 20 行,那么脚本可以负担内存使用。但是如果查询返回数十万行呢?它们都将保存在内存中,以便在高流量站点上,服务器可能会耗尽资源。

唯一的逻辑度量(也是 PDO 可用的唯一选项)是指示数据库计算自身的行数。无论查询有多复杂,都可以重写,使用 SQL COUNT()函数只返回满足主查询的行数。

让我们来看一下在我们的应用程序中使用的查询。(我们将只检查返回多行的查询。)


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

    ```php
    SELECT authors.id AS authorId, firstName, lastName, books.*
    FROM authors, books WHERE author=authors.id ORDER BY title;

    ```

    要获取此查询返回的行数,我们应该将其重写为如下所示:

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

    ```

    请注意,这里不需要 ORDER BY子句,因为顺序对行数实际上并不重要。


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

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

    ```

    这只是改写为以下内容:

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

    ```


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

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

    ```

    这可转化为以下内容:

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

    ```


如您所见,我们以类似的方式重写了所有这些查询,将列列表替换为 COUNT(*)并修剪 ORDER BY子句。考虑到这一点,我们可以创建一个函数,该函数将接受包含要执行的 SQL 的字符串,并返回查询将返回的行数。此函数必须执行以下简单转换:


  • 在传递的字符串中将 SELECTFROM之间的所有内容替换为 COUNT(*)

  • 删除 ORDER BY及其后的所有文本。

实现此转换的最佳方法是使用正则表达式。与前几章一样,我们将使用 PCRE 扩展。我们将把这个函数放到 common.inc.php中,因为我们将在不同的地方调用它:

/**
* 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;
}

让我们运行函数以查看它的功能:


  1. 它将 PDO 连接对象($conn导入本地功能范围。

  2. 它从 SQL 查询的开头和结尾修剪可能的空格。

  3. preg_replace()的两个调用完成了转换查询的主要任务。

请注意我们是如何使用模式修饰符的,s修饰符指示 PCRE 将换行符与点匹配,D修饰符强制$与整个字符串的结尾匹配(不只是在第一个换行符之前)。我们使用这些修饰符来确保函数能够正确处理多行查询。

现在我们将修改这三个脚本,以显示它们返回的每个表中的行数。让我们从 books.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:











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









}
?>
CoverAuthor and TitleISBNPublisherYearSummaryEdit




n/a


("$r[firstName] $r[lastName]")?>



Edit

Add book...
// Display footer
showFooter();

如您所见,修改非常简单,我们使用 $sql变量保存查询并将其传递给 getRowCount()函数和 $conn->query()方法。我们还在表上方显示一条消息,告诉我们数据库中有多少本书。

现在如果您刷新 books.php页面,您将看到以下内容:

Retrieving the Number of Rows in a Result Set

authors.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:








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






}
?>
First NameLast NameBioEdit

Edit

Add Author...
// Display footer
showFooter();

authors.php现在应显示以下内容:

Retrieving the Number of Rows in a Result Set

最后, author.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
?>

Author



















First Name
Last Name
Bio
Total books

Edit author...

Books










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







}
?>
TitleISBNPublisherYearSummary

// Display footer
showFooter();

输出应该是这样的。(为了节省空间,我将页面向下滚动了一点):

Retrieving the Number of Rows in a Result Set

您应该在 common.inc.php中切换 MySQL 和 SQLite,以确保这两个数据库都工作。

这种方法可能适用于许多情况,但并不适用于所有查询。一个这样的例子是使用 GROUP BY子句的查询。如果您使用 getRowCount()函数重写此类查询,您将得到不正确的结果,因为将应用分组,并且查询将返回多行。(行数将等于要分组的列中的不同值数。)

限制返回的行数

现在,当我们知道如何计算结果集中的行数时,让我们看看如何仅获取前 N 行。这里我们有两个选择:


  • 我们可以在 SQL 查询本身中使用特定于数据库的功能。

  • 我们可以自己处理结果集,并在获取所需数量的行后停止。


使用特定于数据库的 SQL

如果您主要使用 MySQL,那么您将熟悉 LIMIT x,y子句。例如,如果我们想获取按姓氏排序的前五位作者,可以发出以下查询:

SELECT * FROM authors ORDER BY lastName LIMIT 0, 5;

以下查询也可以执行相同的操作:

SELECT * FROM authors ORDER BY lastName LIMIT 5 OFFSET 0;

第一个查询适用于 MySQL 和 SQLite,第二个查询也适用于 PostgreSQL。但是,Oracle 或 MS SQL Server 之类的数据库不使用这种语法,因此这些查询将失败。

仅处理前 N 行

如您所见,特定于数据库的 SQL 不允许我们以独立于数据库的方式解决执行分页的任务。但是,我们可以像对所有行一样发出查询,而不需要 LIMIT....OFFSET子句。获取每一行后,我们可以增加计数器变量,以便在处理所需数量的行时中断循环。以下代码段可以用于此目的:

$q = $conn->query("SELECT * FROM authors ORDER BY lastName,
firstName");
$q->setFetchMode(PDO::FETCH_ASSOC);
$count = 1; while(($r = $q->fetch()) && $count <= 5)
{
echo $r['lastName'], '
';
$count++;
} $q->closeCursor();
$q = null;

注意循环条件,它检查计数器变量是否小于或等于 5。(当然,您可以在那里输入任何数字),并且它会验证是否仍有行要获取,因为如果没有更多行,我们必须中断循环。(例如,如果表只有 3 行,我们想显示其中的 5 行,我们应该在最后一行之后中断,而不是在计数器达到 5 行之后。)请注意,使用特定于数据库的 SQL 可以解决这种情况。

另一件重要的事情是对 PDOStatement::closeCursor()的调用(如前一段代码中的最后一行)。有必要告诉数据库我们不需要更多的行。如果不这样做,在同一 PDO 对象上发出的后续查询将导致异常,因为数据库管理系统无法处理新查询,而它们仍在发送前一查询中的行。这就是为什么我们必须在 author.php中调用此方法。

目前(对于 PHP 版本 5.2.1),可能需要通过将语句对象赋值为 null 来取消设置语句对象(如 author.php第 17 行)。另一方面,2007 年 4 月 1 日左右发布的至少一个 CVS 快照根本不需要关闭光标。但是,在使用完光标后调用 PDOStatement::closeCursor()仍然是一种很好的做法。

以任意偏移量开始

现在我们知道了如何处理指定数量的行,我们可以使用相同的技术跳过特定数量的行。假设我们想显示从第 6 页到第 10 页的作者(就像我们在显示第 2 页时,页面大小允许每页有 5 位作者):

$q = $conn->query("SELECT * FROM authors ORDER BY lastName,
firstName");
$q->setFetchMode(PDO::FETCH_ASSOC);
$count = 1;
while(($r = $q->fetch()) && $count <= 5)
{
$count++;
}
$count = 1;
while(($r = $q->fetch()) && $count <= 5)
{
echo $r['lastName'], '
';
$count++;
}
$q->closeCursor();
$q = null;

这里,第一个循环用于跳过必要的起始行,第二个循环显示请求的行子集。

这种方法可能适用于小型表,但其性能并不好。您应该始终使用特定于数据库的 SQL 来返回结果行的子集。如果需要数据库独立性,则应检查底层数据库软件并发出特定于数据库的查询。这样做的原因是,数据库可以对查询执行某些优化,使用更少的内存,以便在服务器和客户端之间交换更少的数据。

不幸的是,PDO 并没有提供独立于数据库的方法来有效地获取结果行的子集,因为 PDO 是一种连接抽象,而不是数据库抽象工具。如果您需要编写可移植代码,您应该探索 MDB2 等工具。

这种方法似乎比使用 PDOStatement::fetchAll()方法更复杂。实际上,我们可以将前面的代码重写如下:

$stmt = $conn->query("SELECT * FROM authors ORDER BY lastName,
firstName");
$page = $stmt->fetchAll(PDO::FETCH_ASSOC);
$page = array_slice($page, 5, 5);
foreach($page as $r)
{
echo $r['lastName'], '
';
}

虽然这段代码要短得多,但它有一个主要缺点:它指示 PDO 返回表中的所有行,然后获取其中的一部分。在我们的方法中,不必要的行被丢弃,循环指示数据库在返回足够的行后立即停止发送行。但是,在这两种情况下,数据库都必须向我们发送当前页面前面的行。

总结

在本章中,我们已经了解了如何处理无缓冲查询以及如何获取结果集的行数。我们还研究了一个无法避免特定于数据库的 SQL 的应用程序,因为这需要一个可能不合适的变通方法。但是,本章对于开发使用数据库的复杂 web 应用程序的人员应该有所帮助。

在下一章中,我们将讨论 PDO 的高级功能,包括持久连接和其他特定于驱动程序的选项。我们还将讨论事务,并研究更多的 PDOPDOStatement类的方法。


推荐阅读
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
author-avatar
大果粒和小果粒
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有