【PHP】PDOでSQLを実行してデータを取得する方法

phpアイキャッチ PHP

こんにちは、かつコーチです。

前回の記事で、PDOを使ってMySQLに接続できるようになりました。

接続できただけではまだ何もできないので、今回は実際にSQL文を実行して、sample_dbusersテーブルからデータを取り出してみましょう。

queryfetchfetchAllという、PDOでのデータ取得の基本を押さえていきます。

SQLを実行する2つの方法

queryメソッドとexecメソッドの違い

PDOには、SQLを実行するためのメソッドが主に2つあります。

メソッド用途戻り値
query()SELECTなど、結果セットを返すSQLを実行するPDOStatementオブジェクト
exec()INSERTUPDATEDELETEなど、影響を受けた行数を知りたい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()で固定し、取得形式は引数(フェッチモード)で指定する、というルールを覚えておくと、この手の混同を防ぎやすくなります。

mysqliPDOは別物なので、検索した記事がどちらの書き方をしているか、コードを見る前に確認する習慣もつけておきましょう。

応用:件数だけ・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を指定しないと扱いにくい配列になるので明示する
  • mysqlifetch_assoc()とPDOのfetch(PDO::FETCH_ASSOC)を混同しないよう注意する
  • 件数や単一の値だけが欲しい場合はCOUNT()fetchColumn()が便利

次に読むべき記事

ここまでは固定のSQL文をそのまま実行してきましたが、実際のアプリではユーザーが入力した値をSQLに組み込む場面がほとんどです。

そのやり方を間違えると、非常に危険な脆弱性につながります。

→ 次の記事:プリペアドステートメントでSQLインジェクションを防ぐ方法

タイトルとURLをコピーしました