关于 pdo 的最后一篇文章,我们就以查询结果集的操作为结束。在数据库的操作中,查询往往占的比例非常高。在日常的开发中,大部分的业务都是读多写少型的业务,所以掌握好查询相关的操作是我们学习的重要内容。和 mysqli 一样,pdo 对于查询的支持也是非常方便快捷的,通过几个函数就可以非常方便高效地操作各种查询语句。

在使用预处理语句的情况下,我们使用 execute() 执行之后,查询的结果集就会保存在 pdostatement 对象中。对于数据的操作就转移到了 php 的对象中,所以我们需要 pdostatement 的一些方法来获得结果集的内容。

fetch() 方法

通过 fetch() 方法,获得的是查询结果集的下一行。

$stmt = $pdo->prepare("select * from zyblog_test_user");
$stmt->execute();

$row = $stmt->fetch();
print_r($row);
// array
// (
//     [id] => 1
//     [0] => 1
//     [username] => aaa
//     [1] => aaa
//     [password] => aaa
//     [2] => aaa
//     [salt] => aaa
//     [3] => aaa
// )

从返回的结果来看,我们没有给 pdo 对象指定 pdo::attr_default_fetch_mode 属性,所以它是返回的默认的 pdo::fetch_both 格式,也就是字段名和下标同时存在的。其实这个方法可以直接指定我们需要的 fetch_style 。

结果集类型指定

$row = $stmt->fetch(pdo::fetch_assoc);
print_r($row);
// array
// (
//     [id] => 2
//     [username] => bbb
//     [password] => bbb
//     [salt] => 123
// )

$row = $stmt->fetch(pdo::fetch_lazy);
print_r($row);
// pdorow object
// (
//     [querystring] => select * from zyblog_test_user
//     [id] => 3
//     [username] => ccc
//     [password] => bbb
//     [salt] => c3
// )

$row = $stmt->fetch(pdo::fetch_obj);
print_r($row);
// stdclass object
// (
//     [id] => 4
//     [username] => ccc
//     [password] => bbb
//     [salt] => c3
// )

和指定 pdo 对象的 pdo::attr_default_fetch_mode 一样。使用 fetch() 方法时直接将需要的返回结果类型参数指定到方法的第一个参数,就实现了 fetch_style 的指定。具体支持的格式和之前讲过的 pdo 对象的 pdo::attr_default_fetch_mode 属性是完全一样的,大家可以自行查阅。

获取全部数据

从代码和定义中可以看出,fetch() 方法是获取当前数据集的下一行数据,就像数据库的游标操作一样。所以,我们可以通过循环 fetch() 来对结果集进行遍历,从而获得所有的结果集数据。

 while($row = $stmt->fetch()){
    print_r($row);
}
// array
// (
//     [id] => 2
//     [0] => 2
//     [username] => bbb
//     [1] => bbb
//     [password] => bbb
//     [2] => bbb
//     [salt] => 123
//     [3] => 123
// )
// ……

mysql 不支持游标

上文中提到了游标操作,pdo 扩展是支持游标的,但是需要注意的是,mysql 扩展并不支持这个操作。所以我们使用游标相关的属性对于 mysql 库是没有效果的。

$stmt = $pdo->prepare("select * from zyblog_test_user", [pdo::attr_cursor => pdo::cursor_scroll]);
$stmt->execute();

$row = $stmt->fetch(pdo::fetch_assoc, pdo::fetch_ori_next);
print_r($row);
// array
// (
//     [id] => 1
//     [username] => aaa
//     [password] => aaa
//     [salt] => aaa
// )

$stmt = $pdo->prepare("select * from zyblog_test_user", [pdo::attr_cursor => pdo::cursor_scroll]);
$stmt->execute();

$row = $stmt->fetch(pdo::fetch_assoc, pdo::fetch_ori_last);
print_r($row);
// array
// (
//     [id] => 1
//     [username] => aaa
//     [password] => aaa
//     [salt] => aaa
// )

如果是支持游标操作的数据库及扩展的话,上面代码中的 fetch() 的第二个参数指定后,获取的结果是会不同的。pdo::fetch_ori_next 是获取游标的下一条数据,而 pdo::fetch_ori_last 是获取游标的最后一条数据。但是在我们对 mysql 的测试中,它们并没有任何效果,依然是获取结果集的下一条数据。

fetchall() 方法

通过 fetch() 方法,我们可以获得结果集中的全部数据,不过还是需要一个循环才能进行遍历,多少还是有点麻烦。其实,pdo 早就为我们准备好了另一个方法,fetchall() 就是返回一个包含结果集中所有行的数组。

$stmt = $pdo->prepare("select * from zyblog_test_user limit 2");
$stmt->execute();

$list = $stmt->fetchall();
print_r($list);
// array
// (
//     [0] => array
//         (
//             [id] => 1
//             [0] => 1
//             [username] => aaa
//             [1] => aaa
//             [password] => aaa
//             [2] => aaa
//             [salt] => aaa
//             [3] => aaa
//         )

//     [1] => array
//         (
//             [id] => 2
//             [0] => 2
//             [username] => bbb
//             [1] => bbb
//             [password] => bbb
//             [2] => bbb
//             [salt] => 123
//             [3] => 123
//         )

// )

fetchall() 就是在内部使用了 fetch() 帮我们遍历了一次结果集并且赋值到了一个数组中。所以我们如果在不重新 execute() 情况下再次调用 fetchall() 的话,获取的就是空的数据。因为游标已经到底了。

$list = $stmt->fetchall();
print_r($list);
// array
// (
// )

它同样支持指定 fetch_style ,也和 fetch() 方法一样,直接将需要的类型常量赋值给第一个参数就可以了。

// pdo::fetch_assoc
$stmt = $pdo->prepare("select * from zyblog_test_user limit 2");
$stmt->execute();

$list = $stmt->fetchall(pdo::fetch_assoc);
print_r($list);
// array
// (
//     [0] => array
//         (
//             [id] => 1
//             [username] => aaa
//             [password] => aaa
//             [salt] => aaa
//         )

//     [1] => array
//         (
//             [id] => 2
//             [username] => bbb
//             [password] => bbb
//             [salt] => 123
//         )

// )

// pdo::fetch_column
$stmt = $pdo->prepare("select * from zyblog_test_user limit 2");
$stmt->execute();

$list = $stmt->fetchall(pdo::fetch_column, 1);
print_r($list);
// array
// (
//     [0] => aaa
//     [1] => bbb
// )

// pdo::fetch_class
class user{
    function __construct($a){
        echo $a, php_eol;
    }
}
$stmt = $pdo->prepare("select * from zyblog_test_user limit 2");
$stmt->execute();
$list = $stmt->fetchall(pdo::fetch_class, 'user', ['fetchall user']);
print_r($list);
// fetchall user
// fetchall user
// array
// (
//     [0] => user object
//         (
//             [id] => 1
//             [username] => aaa
//             [password] => aaa
//             [salt] => aaa
//         )

//     [1] => user object
//         (
//             [id] => 2
//             [username] => bbb
//             [password] => bbb
//             [salt] => 123
//         )

// )

是不是非常熟悉了,这里就不多讲了,关于 fetch_style 的类型指定已经说过很多遍了,它的用法和 fetch() 以及 pdo 对象中的 query() 方法都是差不多的。不过它还支持一种以回调方式调用一个方法的形式来获得数据集。

function getvalue(){
    print_r(func_get_args());
}
// array
// (
//     [0] => 1
//     [1] => aaa
//     [2] => aaa
//     [3] => aaa
// )
// array
// (
//     [0] => 2
//     [1] => bbb
//     [2] => bbb
//     [3] => 123
// )
$stmt = $pdo->prepare("select * from zyblog_test_user limit 2");
$stmt->execute();
$list = $stmt->fetchall(pdo::fetch_func, 'getvalue');
print_r($list);
// array
// (
//     [0] => 
//     [1] => 
// )

在这段代码中,我们使用的是 pdo::fetch_func ,第二个参数是一个方法名称。这样每一条结构集都会在遍历的时候作为方法的参数去调用指定的这个方法,我们通过 func_get_args() 就可以获取到这些参数内容。在这段代码中,结果集并不会通过 fetchall() 方法的返回值赋值给 $list 变量了。因为数据都已经传递给了指定的 getvalue() 方法了。

fetchcolumn() 方法

在上面的测试代码中,我们使用过 pdo::fetch_column 来获取结果集的某一列数据。这样写没什么问题,但是还有更方便的方式,也就是 pdostatment 直接为我们提供的一个 fetchcolumn() 方法。它就相当于是默认的在方法内部指定了 pdo::fetch_column ,并且只需要一个参数就是列的下标。

需要注意的是,它的返回是下一行的指定列值,也就是说,它在底层是调用的 fetch() 方法。如果要获取结果集中所有指定列的内容,我们还需要通过和 fetch() 的遍历方式一样的方法来遍历结果集。

// fetchcolumn
$stmt = $pdo->prepare("select * from zyblog_test_user");
$stmt->execute();
$column = $stmt->fetchcolumn(2);
echo $column, php_eol;
// aaa

$column = $stmt->fetchcolumn(3);
echo $column, php_eol;
// 123

fetchobject() 方法

fetchobject() 就不用多解释了,它和 fetchcolumn() 是类似的,只是返回的是下一行数据的对象格式。同样的,它也是可以传递构造参数的,这点和 pdo 对象的 query() 中指定的 pdo::fetch_class 格式的使用是一样的。我们在第一篇文章中就有讲解。

// fetchobject
$stmt = $pdo->prepare("select * from zyblog_test_user");
$stmt->execute();
$user = $stmt->fetchobject('user', ['fetchobject user']);
print_r($user);
// fetchobject user
// user object
// (
//     [id] => 1
//     [username] => aaa
//     [password] => aaa
//     [salt] => aaa
// )

rowcount() 返回查询结果数量

要获得查询的结果集行数就需要我们的 rowcount() 方法了。数据库中不管是查询还是增、删、改操作,都会返回语句执行结果,也就是受影响的行数。这些信息都是通过 rowcount() 这个方法获得的。

查询语句返回行数

需要注意的是,在查询语句中,有些数据是可能返回此语句的行数的。但这种方式不能保证对所有数据有效,且对可移植的应用更不要依赖这种方式。我们如果需要知道当前查询结果的数量,还是通过遍历 fetch() 或者通过 count(fetchall()) 来根据真实查询到的结果集数量确定这一次查询的真实行数。

其实它就像是 pdo 对象的 exec() 方法所返回的数据。在不使用预处理语句的情况下,直接使用 pdo 的 exec() 方法执行 sql 语句后,返回的也是语句执行后受影响的行数。

$stmt = $pdo->prepare("select * from zyblog_test_user");
$stmt->execute();
$rowcount = $stmt->rowcount();
echo $rowcount, php_eol;
// 41

增、删、改语句返回受影响的行数

$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(?, ?, ?)");
$stmt->execute(['kkk','666','k6']);
$rowcount = $stmt->rowcount();
echo $rowcount, php_eol; // 1
$id = $pdo->lastinsertid();
echo $rowcount, php_eol; // 1

$stmt = $pdo->prepare("update zyblog_test_user set username=? where username = ?");
$stmt->execute(['ccc','cccc']);
$rowcount = $stmt->rowcount();
echo $rowcount, php_eol; // 25

$stmt = $pdo->prepare("update zyblog_test_user set username=? where username = ?");
$stmt->execute(['ccc','cccc']);
$rowcount = $stmt->rowcount();
echo $rowcount, php_eol; // 0

$stmt = $pdo->prepare("delete from zyblog_test_user where username = ?");
$stmt->execute(['ddd']);
$rowcount = $stmt->rowcount();
echo $rowcount, php_eol; // 11

$stmt = $pdo->prepare("delete from zyblog_test_user where username = ?");
$stmt->execute(['ddd']);
$rowcount = $stmt->rowcount();
echo $rowcount, php_eol; // 0

更新和删除操作在数据不存在、没有更新、没有删除的情况下都返回的是 0 。这一点我们也在 pdo 相关的第一篇文章中就说过了,对于业务来说,这种更新或删除到底算是成功还是失败呢?还是大家根据自己的实际业务情况来确定吧!

总结

关于 pdo 和 pdostatement 相关的内容就学习到这里了。我们完整地梳理了一遍它们两个所有的方法,也都进行了相关的测试。大家在日常使用中可能接触到的并不多,框架都已经为我们封装好了。不过对于学习来说,平常的小测试、小调试完全可以自己手写来加深记忆和理解。在深入理解了这些扩展类的使用方法后,反过来又能帮助我们更加的清楚框架是如何去封装它们的。总之,学习就是不断的从高层到底层,再从底层返回高层,循环往复,才能更加的得心应手。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202009/source/php%e4%b8%ad%e7%9a%84pdo%e6%93%8d%e4%bd%9c%e5%ad%a6%e4%b9%a0%ef%bc%88%e5%9b%9b%ef%bc%89%e6%9f%a5%e8%af%a2%e7%bb%93%e6%9e%84%e9%9b%86.php

参考文档:

关注公众号:【硬核项目经理】获取最新文章

添加微信/qq好友:【xiaoyuezigonggong/149844827】免费得php、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

b站id:482780532