PHP入門 データベース 結果セットの取得

結果セットとは、SQLのSELECT命令によって、1個または複数のテーブルから取り出されたレコード群を保持するために、メモリ上に用意された仮想テーブルのことを言います。

1個のテーブルをまるごと結果セットとして取り出すこともありますが、テーブルの列または行を部分的に取り出したり、複数のテーブルを結合(加工)したりするのが一般的です。

結果セットの取得方法

例:結果セット取得


<?php 
require_once './DbManager.php'; 
require_once './Encode.php';
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>PHPサンプル</title>
</head>
<body>
<table class="table">
<thead>
    <tr>
        <th>ISBNコード</th>
        <th>書名</th>
        <th>価格</th>
        <th>出版社</th>
        <th>刊行日</th>
    </tr> 
</thead>
<tbody>
<?php
try {
// データベースへの接続を確立
$db = getDb();
// SELECT命令の実行
$stt = $db->prepare('SELECT * FROM book ORDER BY published DESC');//➊
$stt->execute();                                                  //➊
// 結果セットの内容を順に出力
while($row = $stt->fetch (PDO::FETCH_ASSOC)) {
?>
<tr>
<td><?=e($row['isbn']) ?></td>
<td><?=e($row['title']) ?></td>
<td><?=e($row['price']) ?></td> 
<td><?=e($row['publish']) ?></td> 
<td><?=e($row['published']) ?></td>
</tr>
<?php
    }
} catch (PDOException $e) {
die("エラーメッセージ: {$e->getMessage()}");
}
?>
</tbody>
</table>
</body>
</html>
実行結果
実行結果

結果セットを取得するには、まず(❶)prepare/executeメソッドを利用します。executeメソッドは、取得した結果セットを自分自身(PDOStatementオブジェクト)にセットします。

結果セットを取得できたら、結果セットは2次元表のため、行単位に読み出す必要があります。現在読み込み可能なレコードを表す内部的な目印のことをレコードポインター、また、レコードポインターが示す現在行のことをカレントレコードと言います。PDOStatementオブジェクトには、レコードポインターを次のレコードに移動しながら、移動した先のレコードを読み込むための便利なメソッドが用意されています。それがfetchメソッドです。なお、結果セットから個々の行を取り出すことをフェッチすると言います。

レコードポインターが次のレコードに移動できない場合は、fetchメソッドはfalseを返します。whileループを使用して、結果セット内のレコードを抽出しています。

構文:fetchメソッド

PDOStatement::fetch([int $style = PDO::FETCH_BOTH]):mixed
$styleフェッチモード

フェッチモードとは、結果セットから取り出したデータをどのような形式の変数に格納するかを表すパラメータです。

主なフェッチモード(fetchメソッドの引数)

定数取得の形式値の取得(例)
PDO::FETCH_ASSOC連想配列$row[‘name’]
PDO::FETCH_NUM一般配列$row[0]
PDO::FETCH_BOTH一般配列/連想配列$row[0]/$row[‘name’]
PDO::FETCH_NAMED連想配列$row[‘name’]
PDO::FETCH_OBJオブジェクト$row->name
PDO::FETCH_COLUMNスカラー値$row
PDO::FETCH_BOUNDbindColumnメソッドでバインドされたPHP変数$name
PDO::FETCH_CLASS指定されたフェッチ$row->name

サンプルでは、PDO::FETCH_ASSOCでフェッチしているので、個別のフィールドには$row[‘name’]のようにアクセスできます。フェッチモードは、fetchメソッドで個別に宣言する他にも、setFetchModeメソッドで指定することもできます。

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

結果セットはforeachでも処理できる

結果セットは反復可能なオブジェクトなので、foreachメソッドでも処理することもできます。フェッチモードを変更する必要があるので、foreachループ前でsetFetchModeメソッドを呼び出します。

$stt->setFetchMode(PDO::FETCH_ASSOC);
foreach($stt as $row){
・
・
・
プレイスホルダーを使わない場合は、prepare/executeメソッドの代わりに、queryメソッドを使ってもかまいません。queryメソッドは、指定されたSQL命令を直接実行するので、固定的なSQL命令であればシンプルに記述できます。
$stt = $db->query('SELECT * FROM book ORDER BY published DESC');

フェッチメソッド

データベースから得られた結果セットの内容を取得するために、PDOではさまざまなフェッチメソッドを用意しています。

fetchAllメソッド

fetchAllメソッドを利用することで、結果セットの内容をまとめて配列に変換できます。

構文:fetchAllメソッド

PDOStatement::fetchAll([int $fetch_style = PDO::FETCH_BOTH]): array|false
$fetch_styleフェッチモード

結果セットの内容を素早く確認したいケースなどに使用します。(配列であれば、print_r関数で表示できます)

<?php 
require_once './DbManager.php'; 
$db = getDb();
$stt = $db->query('SELECT * FROM book');
print_r($stt->fetchAll(PDO::FETCH_ASSOC));
実行結果
実行結果

fetchColumnメソッド

fetchColumnメソッドは、結果セットの先頭列のみを取得します。

構文:fetchColumnメソッド

PDOStatement::fetchColumn([int $column_number = 0]): mixed
$column_number取得する列のインデックス番号(先頭列は0)

結果セットの内容が1列しかない(または1行1列しかない)ことが最初からわかっている場合に、このメソッドを利用します。


<?php 
require_once './DbManager.php'; 
$db = getDb();
$stt = $db->query('SELECT count(*) FROM book');
print "件数:{$stt->fetchColumn()}";
実行結果
実行結果

結果セットに複数の列が含まれる場合にも、fetchColumnメソッドは既定で先頭行を取得します。

fetchObjectメソッド

fetchObjectメソッドは、フェッチした結果を指定のオブジェクトのプロパティにセットするメソッドです。

構文:fetchObjectメソッド

PDOStatement::fetchObject([string $class_name = "stdClass"[, array $ctor_args]]): object|false
$class_nameフェッチ先となるクラス名。既定では標準クラス
$ctor_argsクラスおwインスタンス化する際に引き渡すパラメーター

bookテーブルから取得した値を、Bookクラスに渡す例です。Bookクラスには、インスタンス化のタイミングで値引き率を渡せるものとします。また、値引き後の価格はBookクラスのdiscountメソッドで取得できるものとします。

fetchObjectメソッドは、フェッチモードFETCH_OBJ/FETCH_CLASSで代替することもできます。ただし、クラス名を明示する場合には、setFetchModeメソッドであらかじめ宣言する必要があります。

$stt->setFetchMode(PDO::FETCH_CLASS, 'Book', [ 0.1 ]);
if ($row = $stt->fetch()){

sample1.php


<?php 
require_once './DbManager.php'; 
require_once 'Book.php';

$db = getDb();
$stt = $db->query("SELECT * FROM book where isbn='4768320001'");
if($row = $stt->fetchObject('Book', [0.1])){
    print "{$row->title}:{$row->discount()}円";
}

book.php

<?php
class Book{
    //フィールド値を設定/取得するためのプロパティ
    public $isbn;
    public $title;
    public $price;
    public $publish;
    public $published;
    public $_rate;

    //インスタンス化時に値引き率をセット
    public function __construct(float $rate){
        $this->_rate = $rate;
    }
    public function discount():float{
        return floor($this->price * (1 - $this->_rate));
    }
}
実行結果
実行結果

フェッチモードの捕捉

fetch/fetchAllなどのメソッドでは、フェッチに際してレコードの取得形式(フェッチモード)を指定できます。利用可能なフェッチモードの捕捉について記載します。

PDO::FETCH_ASSOC/PDO::FETCH_NAMED

PDO::FETCH_ASSOC/PDO::FETCH_NAMEDは、どちらもレコードを連想配列の形式で取得するという意味では共通しています。両者が異なるのは、テーブル連結などでフィールド名が重複した場合です。PDO::FETCH_ASSOCではフィールド名が重複した場合、先に登場したフィールドが後ろのフィールドで上書きされます。それに対して、PDO::FETCH_NAMEDでは、入れ子の配列として双方の値を返します。

PDO::FETCH_BOUND

PDO::FETCH_BOUNDは、フェッチした各フィールドの値を、あらかじめマッピングしておいた変数に設定します。マッピングに際してはデータ型の定義もできるので、主にデータベースから取得した値を厳密な型指定の下で操作したいようなケースで使用します。

photoテーブル

フィールド名データ型概要
idINT写真ID(主キー/自動連番)
typeVARCHAR(50)ファイルの種類
dataMEDIUMBLOB写真データ

フェッチモードPDO::FETCH_BOUNDを使用する場合は、あらかじめbindColumnメソッドで取得列と変数のマッピングを宣言しておく必要があります。

例:データベースに登録されている画像データをブラウザに表示する


<?php
require_once './DbManager.php';
try {
    // データベースへの接続を確立
    $db = getDb();
    // クエリ情報idをキーに、photoテーブルから画像データを取得(無指定の場合は1) 
    $stt = $db->prepare('SELECT * FROM photo WHERE id = ?'); 
    $stt->bindValue(1, $_GET['id'] ?: 1);
    $stt->execute();
    //取得列と変数とをマッピング (type列は変数$type、data列は$dataに割り当て) 
    $stt->bindColumn('type', $type, PDO::PARAM_STR);
    $stt->bindColumn('data', $data, PDO::PARAM_LOB);
    // フェッチモード PDO::FETCH_BOUNDでレコードを取得
    if ($stt->fetch(PDO::FETCH_BOUND)) {
        // 取得に成功した場合は、ブラウザーに取得データを出力 
        header("Content-Type: {$type}");
        print $data;
    } else {
        // 該当するレコードが存在しない場合はエラーメッセージを表示
print '該当するデータがありません。 ';
    }
} catch (PDOException $e) {
    die("エラーメッセージ: {$e->getMessage()}");
}

構文:bindColumnメソッド

PDOStatement::bindColumn(mixed $column, mixed &$param[, int $type [, int $maxlen]]): bool
$columnフィールド名またはインデックス番号(1スタート)
&$paramフィールド値をバインドする変数
$typeデータ型
$maxlenデータの最大長

引数$typeに指定できるデータ型

パラメーターのデータ型(bindValueメソッド)

定数概要
PDO::PARAM_BOOL真偽型
PDO::PARAM_NULLNULL型
PDO::PARAM_INT整数型
PDO::PARAM_STR文字列型(既定)
PDO::PARAM_LOBラージオブジェクト型
PDO::PARAM_STMTSQLクエリ
PDO::PARAM_INPUT_OUTPUT入出力パラメーター(「PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT」のように他の定数と組み合わせでのみ使用)

データ型を明示した場合、PDOはフェッチに際して取得列が指定されたデータ型であること前提にバインド処理します。bindColumnメソッドで変数の割り当てができてしまえば、あとはfetchメソッドで実際にレコードをフェッチするだけです。

PDO::FETCH_UNIQUE/PDO::FETCH_GROUP

フェッチモードPDO::FETCH_UNIQUE/PDO::FETCH_GROUPは、fetchAllメソッドとフェッチモードPDO::FETCH_COLUMNとの組み合わせで利用できる特殊なフェッチモードです。

PDO::FETCH_UNIQUE/PDO::FETCH_GROUPは、いずれも取得した結果セットの先頭列がキーに、2列目が値となるよに連想配列として、結果セットをフェッチします。両者の違いは、キーの値が重複している場合です。キーの値が重複している場合、PDO::FETCH_UNIQUEでは最後の値だけが結果に表れますが、PDO::FETCH_GROUPではすべての値を入れ子の配列として返します。