こんにちは、かつコーチです。
前回の記事で、PDOを使ってMySQLに接続できるようになりました。
接続できただけではまだ何もできないので、今回は実際にSQL文を実行して、sample_dbのusersテーブルからデータを取り出してみましょう。
query・fetch・fetchAllという、PDOでのデータ取得の基本を押さえていきます。
SQLを実行する2つの方法
queryメソッドとexecメソッドの違い
PDOには、SQLを実行するためのメソッドが主に2つあります。
| メソッド | 用途 | 戻り値 |
|---|---|---|
query() | SELECTなど、結果セットを返すSQLを実行する | PDOStatementオブジェクト |
exec() | INSERT・UPDATE・DELETEなど、影響を受けた行数を知りたいSQLを実行する | 影響を受けた行数(整数) |
今回扱うのはデータの取得なので、query()を使っていきます。
exec()は次のCRUD実装の記事で登場します。
固定のSQLをqueryで実行する
まずは一番シンプルな例から見てみましょう。
<?php
$dsn = 'mysql:host=localhost;dbname=sample_db;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->query('SELECT * FROM users');
?>
$pdo->query()にSQL文を渡すと、実行結果を持ったPDOStatementというオブジェクトが返ってきます。
このオブジェクトの中に「取得したデータ」が入っているイメージですが、まだこの時点では中身を取り出せていません。
データを取り出すには、この後説明するfetch系のメソッドを使う必要があります。
fetchとfetchAllでデータを取り出す
fetch()で1行ずつ取り出す
fetch()は、結果セットから1行分のデータを配列として取り出すメソッドです。
<?php
$stmt = $pdo->query('SELECT * FROM users');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['name'] . '(' . $row['email'] . ')' . PHP_EOL;
}
?>
実行すると、以下のように出力されます。
田中太郎(tanaka@example.com)
佐藤花子(sato@example.com)
fetch()を呼ぶたびに「次の1行」が返され、データがなくなるとfalseが返るので、whileループと組み合わせて全件を順番に処理するのが基本形です。
fetchAll()で全件まとめて取り出す
1件ずつではなく、最初から全件を配列として受け取りたい場合はfetchAll()を使います。
<?php
$stmt = $pdo->query('SELECT * FROM users');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($users as $user) {
echo $user['name'] . PHP_EOL;
}
echo "全" . count($users) . "件のユーザーがいます";
?>
fetchAll()は結果を丸ごと配列にして返してくれるので、件数を数えたり、後で何度も配列をループしたりしたい場合に便利です。
ただし件数が非常に多いテーブルに対してfetchAll()を使うと、メモリを大量に消費することがあるため、大量データを扱う場面ではfetch()で1行ずつ処理する方法も選択肢に入れておきましょう。
フェッチモードの指定を忘れると連想配列にならない
fetch()やfetchAll()には、PDO::FETCH_ASSOCという引数を渡しています。
このフェッチモードを省略すると、キーが列名(nameなど)だけでなく、0, 1のような数値インデックスでも取れてしまう、少し扱いにくい配列になります。
コードの可読性を保つためにも、PDO::FETCH_ASSOCを明示的に指定する習慣をつけておくのがおすすめです。
前回の記事で紹介したPDO::ATTR_DEFAULT_FETCH_MODEを接続時に設定しておけば、毎回指定しなくても連想配列で取れるようになります。
つまずきやすいポイント:mysqli関数との混同
mysqli時代の書き方を持ち込んでしまう
私が最初にPDOを触ったとき、以前少しだけ触ったmysqliの書き方の記憶につられて、こんなコードを書いてしまったことがあります。
❌ Before:存在しないメソッドを呼んでエラーになる
<?php
$stmt = $pdo->query('SELECT * FROM users');
while ($row = $stmt->fetch_assoc()) { // mysqliの書き方を混同している
echo $row['name'];
}
?>
このコードを実行すると、以下のようなエラーが出ます。
Fatal error: Uncaught Error: Call to undefined method PDOStatement::fetch_assoc()
fetch_assoc()はmysqliの関数名で、PDOには存在しないメソッドです。
✅ After:PDOのメソッドとフェッチモードで正しく書く
<?php
$stmt = $pdo->query('SELECT * FROM users');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['name'];
}
?>
PDOでは、メソッド名はfetch()で固定し、取得形式は引数(フェッチモード)で指定する、というルールを覚えておくと、この手の混同を防ぎやすくなります。
mysqliとPDOは別物なので、検索した記事がどちらの書き方をしているか、コードを見る前に確認する習慣もつけておきましょう。
応用:件数だけ・1件だけ取得したいとき
COUNT関数で件数だけ取得する
全件を取ってきてからcount()で数えるのではなく、SQLの時点で件数だけを取得する方法も知っておくと便利です。
<?php
$stmt = $pdo->query('SELECT COUNT(*) AS total FROM users');
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo "ユーザー数: " . $result['total'] . "人";
?>
COUNT(*)をSQL側で計算させることで、テーブルの行数が多い場合でも、PHP側で全件取得するより効率的に件数を得られます。
fetchColumn()で単一の値だけ取得する
件数や合計金額など、結果が1つの値だけで良い場合はfetchColumn()が便利です。
<?php
$stmt = $pdo->query('SELECT COUNT(*) FROM users');
$total = $stmt->fetchColumn();
echo "ユーザー数: " . $total . "人";
?>
配列を経由せずに直接値を受け取れるので、コードがすっきりします。
まとめ
この記事のポイント
SELECTなど結果を返すSQLはquery()、INSERTなどはexec()で実行するfetch()は1行ずつ、fetchAll()は全件まとめてデータを取得するPDO::FETCH_ASSOCを指定しないと扱いにくい配列になるので明示するmysqliのfetch_assoc()とPDOのfetch(PDO::FETCH_ASSOC)を混同しないよう注意する- 件数や単一の値だけが欲しい場合は
COUNT()やfetchColumn()が便利
次に読むべき記事
ここまでは固定のSQL文をそのまま実行してきましたが、実際のアプリではユーザーが入力した値をSQLに組み込む場面がほとんどです。
そのやり方を間違えると、非常に危険な脆弱性につながります。
→ 次の記事:プリペアドステートメントでSQLインジェクションを防ぐ方法