この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:3
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: SQL実行方法
SEOタイトル: PHP で SQL 実行 (PDO/Prepared Statement) 完全ガイド

この記事の要点
  • 現代の PHP では PDO (PHP Data Objects) が標準。MySQL/PostgreSQL/SQLite 等を共通 API で扱える
  • Prepared Statement + バインドパラメータが SQL Injection 対策の基本
  • 取得: $stmt->fetch(PDO::FETCH_ASSOC) / $stmt->fetchAll()
  • トランザクション: beginTransaction() / commit() / rollBack()
  • mysqli は MySQL 専用で残るが新規は PDO 推奨。Laravel なら Eloquent / Query Builder

PHP での SQL 実行: 3 つの方法

方式対応 DB推奨度
PDOMySQL/PG/SQLite/MSSQL/Oracle 等★★★ 標準
mysqliMySQL/MariaDB のみ★★ 既存システム
Laravel Eloquent / Query BuilderPDO 上のラッパー★★★ Laravel なら必須
mysql_* (旧)MySQLPHP 7 で廃止 - 使用禁止

PDO の基本

接続

 PDO::ERRMODE_EXCEPTION,  // ★ 例外を投げる
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,        // ★ 連想配列で取得
    PDO::ATTR_EMULATE_PREPARES   => false,                   // ★ 本物の Prepared Statement
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8mb4'",
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
    error_log('DB connect failed: ' . $e->getMessage());
    die('Connection failed');
}

// PostgreSQL の場合
$dsn = 'pgsql:host=localhost;port=5432;dbname=mydb';

// SQLite の場合
$dsn = 'sqlite:/var/data/app.db';

SELECT (取得)

// バインドパラメータ (?)
$stmt = $pdo->prepare('SELECT id, name, email FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// → ['id' => 1, 'name' => 'taro', 'email' => 't@b.c']

// 名前付きバインド (:name)
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');
$stmt->execute([':email' => $email, ':status' => 'active']);
$user = $stmt->fetch();

// 全件取得
$stmt = $pdo->prepare('SELECT * FROM users WHERE status = ?');
$stmt->execute(['active']);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 1 行ずつ処理 (大量データ)
$stmt = $pdo->prepare('SELECT * FROM big_table');
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // 1 行ずつ処理 (メモリ効率良い)
}

// 1 カラムだけ取得
$stmt = $pdo->prepare('SELECT COUNT(*) FROM users');
$stmt->execute();
$count = (int) $stmt->fetchColumn();

FETCH_MODE 一覧

モード戻り値
PDO::FETCH_ASSOC連想配列 (推奨)
PDO::FETCH_NUM添字配列 [0=>..., 1=>...]
PDO::FETCH_BOTH両方 (メモリ無駄、非推奨)
PDO::FETCH_OBJstdClass オブジェクト
PDO::FETCH_CLASS指定クラスのインスタンス
PDO::FETCH_COLUMN1 カラムだけ
PDO::FETCH_KEY_PAIR先頭 2 列を key=>value 配列に

INSERT / UPDATE / DELETE

// INSERT
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute(['taro', 't@b.c']);
$lastId = (int) $pdo->lastInsertId();

// 名前付き
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
$stmt->execute([':name' => 'taro', ':email' => 't@b.c']);

// UPDATE (件数取得)
$stmt = $pdo->prepare('UPDATE users SET name = ? WHERE id = ?');
$stmt->execute(['new_name', $id]);
$updatedRows = $stmt->rowCount();

// DELETE
$stmt = $pdo->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$id]);
$deletedRows = $stmt->rowCount();

// 一括 INSERT
$sql = 'INSERT INTO users (name, email) VALUES (?, ?), (?, ?), (?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute(['a', 'a@x', 'b', 'b@x', 'c', 'c@x']);

Prepared Statement と SQL Injection

WHERE 句や VALUES にユーザー入力を直接連結すると SQL Injection 脆弱性になります:

// ❌ 致命的: SQL Injection
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$pdo->query($sql);
// /?id=1 OR 1=1 → 全件取得
// /?id=1; DROP TABLE users -- → テーブル削除

// ❌ 同じく危険: 文字列連結
$name = $_POST['name'];
$pdo->query("SELECT * FROM users WHERE name = '$name'");
// /?name=' OR '1'='1 → 全件取得

// ✅ 安全: Prepared Statement
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_GET['id']]);

$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute([':name' => $_POST['name']]);

// ★ EMULATE_PREPARES = false にすることで本物の Prepared に
// (デフォルトはエミュレートで escape 任せ、本物の方が安全)

bindValue / bindParam で型指定

$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id AND age >= :age');
$stmt->bindValue(':id',  $id,  PDO::PARAM_INT);
$stmt->bindValue(':age', $age, PDO::PARAM_INT);
$stmt->execute();

// 主な型
// PDO::PARAM_INT    : 整数
// PDO::PARAM_STR    : 文字列 (デフォルト)
// PDO::PARAM_BOOL   : 真偽値
// PDO::PARAM_NULL   : NULL
// PDO::PARAM_LOB    : Large Object (BLOB)

// LIMIT に変数を入れるときは bindValue (PARAM_INT) 必須
// (EMULATE_PREPARES=true だと 'LIMIT "10"' になりエラー)
$stmt = $pdo->prepare('SELECT * FROM users LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit',  $limit,  PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();

トランザクション

try {
    $pdo->beginTransaction();

    $pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
        ->execute([1000, $from]);

    $pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')
        ->execute([1000, $to]);

    $pdo->prepare('INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)')
        ->execute([$from, $to, 1000]);

    $pdo->commit();
} catch (Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    error_log($e->getMessage());
    throw $e;
}

// 暗黙コミット (DDL) は要注意
// MySQL: BEGIN 中の CREATE TABLE は暗黙 COMMIT する

mysqli (MySQL 専用)

// 接続
$mysqli = new mysqli('localhost', 'user', 'pass', 'mydb');
$mysqli->set_charset('utf8mb4');

// Prepared Statement
$stmt = $mysqli->prepare('SELECT * FROM users WHERE id = ?');
$stmt->bind_param('i', $id);   // 型: i=int, s=string, d=double, b=blob
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    echo $row['name'];
}

// 多次元 INSERT
$stmt = $mysqli->prepare('INSERT INTO users (name, age) VALUES (?, ?)');
$stmt->bind_param('si', $name, $age);
$stmt->execute();

// 一括処理ループ
foreach ($users as $u) {
    $name = $u['name'];
    $age  = $u['age'];
    $stmt->execute();   // bind_param は参照なので再 bind 不要
}
$stmt->close();
$mysqli->close();

Laravel での SQL 実行

use Illuminate\Support\Facades\DB;
use App\Models\User;

// === Raw SQL ===
$users = DB::select('SELECT * FROM users WHERE status = ?', ['active']);
DB::insert('INSERT INTO logs (msg) VALUES (?)', ['hello']);
$rows = DB::update('UPDATE users SET name = ? WHERE id = ?', ['new', 1]);
$rows = DB::delete('DELETE FROM users WHERE id = ?', [1]);

// === Query Builder ===
$users = DB::table('users')
    ->where('status', 'active')
    ->where('age', '>=', 20)
    ->orderBy('id')
    ->limit(10)
    ->get();

DB::table('users')->insert([
    'name' => 'taro', 'email' => 't@b.c',
]);

DB::table('users')->where('id', 1)->update(['name' => 'new']);
DB::table('users')->where('id', 1)->delete();

// === Eloquent (ORM) ===
$users = User::where('status', 'active')->orderBy('id')->paginate(20);

User::create(['name' => 'taro', 'email' => 't@b.c']);

$user = User::find(1);
$user->name = 'new';
$user->save();

User::destroy(1);

// === Transaction ===
DB::transaction(function () {
    Account::where('id', $from)->decrement('balance', 1000);
    Account::where('id', $to)->increment('balance', 1000);
});

エラーハンドリングのベストプラクティス

// PDO は ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION で例外化
try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchAll();
} catch (PDOException $e) {
    // ログには詳細、ユーザーには概要のみ
    error_log(sprintf(
        '[DB] %s SQL=%s PARAMS=%s',
        $e->getMessage(), $sql, json_encode($params)
    ));
    throw new RuntimeException('Database error', 0, $e);
}

// SQLSTATE で原因判別
catch (PDOException $e) {
    if ($e->getCode() === '23000') {        // Integrity constraint violation
        // 重複等
    } elseif ($e->getCode() === '40001') {  // Serialization failure
        // デッドロック → リトライ
    }
    throw $e;
}

FAQ

Q: PDO と mysqli どちらがいい?
A: 新規は PDO。複数 DB を扱う・将来 PostgreSQL 等に切り替える可能性がある場合は特に PDO 一択です。

Q: EMULATE_PREPARES は true / false どちら?
A: false (本物の Prepared Statement) を推奨。エミュレートだと型変換の罠 (LIMIT に文字列が入る等) があり、二重 escape も発生しやすいです。

Q: PDO で接続が切れる
A: MySQL の wait_timeout によるアイドル切断です。PDO::ATTR_PERSISTENT は問題が多く非推奨、必要なら接続プール (PHP-FPM の Persistent Connection など) を検討。