SELECT文で rowCount() が使えるか?
PDOではなく、各DB固有の関数だと、「SELECT結果の件数」を返す関数として、
- MySQLなら、mysql_num_rows()
- MySQL拡張(MySQLi)なら、mysqli_stmt::num_rows()メソッドまたはmysqli_stmt_num_rows()関数
- PostgreSQLなら、pg_num_rows()関数
があるんですが、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 文の場合、いくつかのデータベースは文によって返された 行数を返すかも知れません。」てことなので、具体的によく使うMySQLやPostgreSQLはどうなのよ?ってとこを調べてみました。
ちなみに実験に使用したコードはこんな感じ。
<?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件ほど突っ込みました。
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()のページに「この関数は非推奨です」って書いてあるべきだよなぁ。。。
この辺がまだもやもやしています。。。