ありがとう。また会おう。

ゆるいかんじで。かたのちからぬいて。やってます。

SELECT文で rowCount() が使えるか?

PDOではなく、各DB固有の関数だと、「SELECT結果の件数」を返す関数として、

があるんですが、PDOの場合、それっぽいメソッドが rowCount()くらいしかないのですが。PHPマニュアルによると。

PDOStatement::rowCount() は 相当する PDOStatement オブジェクトによって実行された 直近の DELETE, INSERT, UPDATE 文によって作用した行数を返します。 関連する PDOStatement によって実行された直近の SQL ステートメントが SELECT 文の場合、いくつかのデータベースは文によって返された 行数を返すかも知れません。しかしながら、 この振る舞いは全てのデータベースで保証されていません。 さまざまな場所で使用するアプリケーションでは、 これに頼ってはいけません。

つまり、このメソッドは、**_num_rows()のPDO版ではなく、**_affected_rows()のPDO版って位置づけなんですね。。。

で、その代替策として

ほとんどのデータベースでは、PDOStatement::rowCount() は SELECT 文によって作用した行数を返しません。代わりに、 PDO::query() を使って 意図する SELECT 文として同様の述部を持つ SELECT COUNT(*) 文を発行し、PDOStatement::fetchColumn() を使って返される行数を取得することができます。

と記述があるんですが・・・
いやね、それはわかってるの。
でも、今まで1回で問い合わせ済んでたのが、PDOにするから2回クエリ投げろってのはあんまりでないかい?


ってことで、最初の引用にある「SELECT 文の場合、いくつかのデータベースは文によって返された 行数を返すかも知れません。」てことなので、具体的によく使うMySQLPostgreSQLはどうなのよ?ってとこを調べてみました。


ちなみに実験に使用したコードはこんな感じ。

<?php
// DB_CONNECT_STR 定数には、それぞれのDBで必要な接続情報が定義されているとして読んで下さい。
try {
    $db = new PDO(DB_CONNECT_STR);    
    $stmt = $db->query('SELECT * FROM hoge_table ');
    var_dump($stmt->rowCount());
 } catch (PDOException $e){
    die($e->getMessage());
 }

hoge_tableには、テストデータを3件ほど突っ込みました。

PostgreSQLの場合

実行結果:

int(3)

おお!返ってきましたよ!
ちなみに使用したバージョンは、PHPが5.1.6。PostgreSQLが8.1。

MySQLの場合:その1

実行結果:

int(0)

う〜ん・・・どうもダメっぽい・・・。
使用したバージョンは、PHP5.1.6、MySQL5.0。


ところが・・・別の環境で試したところ・・・

MySQLの場合:その2

実行結果:

int(3)

あれ?今度は返ってくるぞ?
プログラムは(DB接続文字列以外は)変えてないのに?
使用したバージョンは、PHP5.2.6、MySQL5.0。


さて、この差異はなんなのか?

google先生にいろんな角度(単語)からあ〜でもない、こ〜でもないと聞きまくり、どうやらそれっぽい情報を発見。
PHP5.2.1のChangeLog

  • PDO_MySQL Extension Improvements (Ilia)
    • Enabled buffered queries by default.

そういえば、(PDOでない)MySQL関数の mysql_num_rows()関数のヘルプにも

注意: mysql_unbuffered_query() を使用した場合、 結果セットのすべての行を取得するまで mysql_num_rows() は正しい値を返しません。

ってある。
どうやら、「バッファードクエリ」がポイントのようだ。

MySQLの場合:その3

で、よくよく調べてみると、PDOのMySQL用のオプション設定で、このバッファードクエリを使用するか否かを設定できるらしい。
そこで、先の実験プログラムをちょっとだけ修正。

<?php
// DB_CONNECT_STR 定数には、それぞれのDBで必要な接続情報が定義されているとして読んで下さい。
try {
    $db = new PDO(DB_CONNECT_STR);
    $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); //←この行追加
    $stmt = $db->query('SELECT * FROM hoge_table ');
    var_dump($stmt->rowCount());
 } catch (PDOException $e){
    die($e->getMessage());
 }

追加した行にある、「PDO::MYSQL_ATTR_USE_BUFFERED_QUERY」は詳しくはこちらを:
http://www.php.net/manual/ja/ref.pdo-mysql.php

で、先ほどのPHPバージョン違いの2つの環境(PHP5.1.6/PHP5.2.6)で試したところ・・・

int(3)

よし!両方返ってきた!

結論

2つのDBでしか試してませんが、

  • PostgreSQLならrowCount()メソッドでSELECT文の結果件数が取得できる
  • MySQLは、PHP5.2.1以降なら*1、rowCount()メソッドでSELECT文の結果件数が取得できる
    • それ以前の場合は、PDO::MYSQL_ATTR_USE_BUFFERED_QUERY を true にセットすれば取得可能

ということに。
他のDBは手元に環境がないので、わかりません。

あとは、「DBによっては動かないかもしれない」という、このメソッドを使うかどうかは、そのプロジェクト毎の判断でいいのではないでしょうか。
マニュアルには「どのDBでも動作しなければならない製品の場合は、このメソッドではなく、SELECT count(*)を使え」ってあるんですが、使用するDBが限定できるのであれば、使うという選択もありな気がします。プロジェクト責任者の判断で。
rowCount()使った方が、確実にクエリ投げる回数減りますし、ソースコード的にも読みやすいし。

新たな疑問

PDO::MYSQL_ATTR_USE_BUFFERED_QUERYについて、PHPマニュアルには

PDOStatement でこの属性を TRUE に設定すると、 MySQL ドライバはバッファ版の MySQL API を使用します。 移植性の高いコードを書くには、代わりに PDOStatement::fetchAll() を使用すべきです。

とあるのだが、これって実際のところどうなんだろう?
「移植性の高いコードを書く」ことと「PDOStatement::fetchAll() を使用する」ことが自分の中で繋がらないのだが・・・?

職場の同僚がこの記述を見て、
「PDOはPDOStatement::fetch()は非推奨で、PDOStatement::fetchAll()を使わなければいけない」
と思ってたようなんだけど・・・そうではない気がするんだけど・・・

もし万一そうだったら、PDOStatement::fetch()のページに「この関数は非推奨です」って書いてあるべきだよなぁ。。。


この辺がまだもやもやしています。。。

*1:厳密にはこのバージョンの境界で実験してないんだけど、ChangeLogの記述と、今回の実験で変更している属性の意味を考えると、ほぼこれと断定していいのではないかと。間違ってたらツッコミ入れて下さい。