10.

Laravel Query Builder 完全ガイド(DB ファサード / join / トランザクション /

編集
この記事の要点
  • Query Builder は DB::table("users")->where(...)->get() のように生 SQL をラップして安全に書ける
  • パラメータは自動バインドされ SQL インジェクションを防止。生 SQL を書くなら whereRaw("col = ?", [$x])
  • join / groupBy / having / unionAll も全てメソッドチェーンで表現可能
  • トランザクションは DB::transaction(function () { ... })。例外で自動ロールバック
  • Eloquent との使い分け: 単純な集計・複雑な join は Query Builder、ドメインモデルを介す処理は Eloquent

Query Builder の基本

DB::table() から始めるメソッドチェーン形式で SQL を組み立てます:

use Illuminate\Support\Facades\DB;

// SELECT * FROM users WHERE active = 1
$users = DB::table('users')
    ->where('active', 1)
    ->get();

// SELECT id, name FROM users WHERE age >= 20 ORDER BY name LIMIT 10
$users = DB::table('users')
    ->select('id', 'name')
    ->where('age', '>=', 20)
    ->orderBy('name')
    ->limit(10)
    ->get();

// 単一行 / 単一カラム
$user  = DB::table('users')->where('id', 1)->first();
$name  = DB::table('users')->where('id', 1)->value('name');
$count = DB::table('users')->count();

WHERE 句のバリエーション

メソッド生成される SQL
where('age', '>', 20)age > 20
whereIn('id', [1,2,3])id IN (1,2,3)
whereBetween('age', [20,30])age BETWEEN 20 AND 30
whereNull('deleted_at')deleted_at IS NULL
whereExists(function ($q) {...})EXISTS (SELECT ...)
orWhere('email', $x)OR email = ?
whereRaw('YEAR(created_at) = ?', [2026])生 SQL(パラメータバインド)

JOIN / GROUP BY / HAVING

// 内部結合 + 集計
$stats = DB::table('users')
    ->join('orders', 'users.id', '=', 'orders.user_id')
    ->select('users.name', DB::raw('SUM(orders.amount) AS total'))
    ->groupBy('users.id', 'users.name')
    ->having('total', '>', 10000)
    ->orderByDesc('total')
    ->get();

// 左外部結合
$users = DB::table('users')
    ->leftJoin('profiles', 'users.id', '=', 'profiles.user_id')
    ->select('users.*', 'profiles.bio')
    ->get();

// サブクエリ join
$latest = DB::table('logins')
    ->select('user_id', DB::raw('MAX(login_at) AS last'))
    ->groupBy('user_id');

$users = DB::table('users')
    ->joinSub($latest, 'l', fn($j) => $j->on('users.id', '=', 'l.user_id'))
    ->select('users.name', 'l.last')
    ->get();

selectRaw / whereExists / Union

// selectRaw で集計関数や式を直接書く
$rows = DB::table('orders')
    ->selectRaw('YEAR(created_at) AS year, SUM(amount) AS total')
    ->groupBy(DB::raw('YEAR(created_at)'))
    ->get();

// EXISTS サブクエリ
$users = DB::table('users')
    ->whereExists(function ($q) {
        $q->select(DB::raw(1))
          ->from('orders')
          ->whereColumn('orders.user_id', 'users.id')
          ->where('orders.amount', '>', 10000);
    })
    ->get();

// UNION
$a = DB::table('users')->where('type', 'admin');
$b = DB::table('users')->where('type', 'editor');
$all = $a->unionAll($b)->get();

INSERT / UPDATE / DELETE

// 単一 INSERT
DB::table('users')->insert([
    'name'       => 'Alice',
    'email'      => 'alice@example.com',
    'created_at' => now(),
]);

// 一括 INSERT
DB::table('users')->insert([
    ['name' => 'Bob',     'email' => 'bob@example.com'],
    ['name' => 'Charlie', 'email' => 'charlie@example.com'],
]);

// INSERT して ID を取得
$id = DB::table('users')->insertGetId([
    'name'  => 'Dan',
    'email' => 'dan@example.com',
]);

// UPDATE
$affected = DB::table('users')
    ->where('id', 1)
    ->update(['name' => 'Alice Smith']);

// UPSERT(PHP 8 + Laravel 8+)
DB::table('users')->upsert(
    [['email' => 'a@x.com', 'name' => 'A'], ['email' => 'b@x.com', 'name' => 'B']],
    ['email'],       // 重複検出キー
    ['name']         // 更新対象カラム
);

// DELETE
DB::table('users')->where('active', 0)->delete();

生 SQL を実行: DB::select / DB::statement

// SELECT 系
$rows = DB::select('SELECT * FROM users WHERE active = ?', [1]);

// 戻り値が不要なステートメント (CREATE / ALTER / TRUNCATE)
DB::statement('TRUNCATE TABLE logs');
DB::statement('CREATE INDEX idx_email ON users(email)');

// DDL
DB::unprepared('CREATE TABLE backup_users AS SELECT * FROM users');

// プレースホルダ必須(SQL インジェクション対策)
// ❌ 危険
DB::select("SELECT * FROM users WHERE email = '{$email}'");

// ✅ 安全
DB::select('SELECT * FROM users WHERE email = ?', [$email]);

トランザクション

// クロージャ版(例外で自動ロールバック・推奨)
DB::transaction(function () {
    DB::table('accounts')->where('id', 1)->decrement('balance', 1000);
    DB::table('accounts')->where('id', 2)->increment('balance', 1000);
    DB::table('transfers')->insert([
        'from' => 1, 'to' => 2, 'amount' => 1000,
    ]);
});

// 手動制御版
DB::beginTransaction();
try {
    // ...
    DB::commit();
} catch (\Throwable $e) {
    DB::rollBack();
    throw $e;
}

// デッドロック時のリトライ回数(第 2 引数)
DB::transaction(function () { /* ... */ }, 3);

SQL インジェクション対策

Query Builder はすべてのパラメータを自動バインドします。whereRaw でも第 2 引数の配列にすればバインドされます:

// ✅ 自動バインド
DB::table('users')->where('email', $email)->get();
// → SELECT * FROM users WHERE email = ?
//   バインド: [$email]

// ✅ whereRaw でもバインドできる
DB::table('users')->whereRaw('LOWER(email) = ?', [strtolower($email)])->get();

// ❌ 絶対やってはいけない(文字列連結)
DB::select("SELECT * FROM users WHERE email = '$email'");

Eloquent との使い分け

場面推奨理由
1 件取得 → 加工 → 保存Eloquentモデルのビジネスロジックを通す
複雑な集計レポートQuery BuilderSQL を直に書く方が見通し良い
大量バルク INSERTQuery Builder + insert()Eloquent はモデル生成のオーバヘッドあり
リレーション込みの取得Eloquent + with()N+1 回避が容易
外部 DB の参照Query Builderモデル不要

発行された SQL を確認

// toSql() で生 SQL(プレースホルダのまま)
$sql = DB::table('users')->where('id', 1)->toSql();
// → SELECT * FROM users WHERE id = ?

// バインドパラメータ
$bindings = DB::table('users')->where('id', 1)->getBindings();
// → [1]

// クエリログを取る(開発時のみ)
DB::enableQueryLog();
DB::table('users')->get();
dd(DB::getQueryLog());

FAQ

Q: get() と first() と value() の違い
A: get() はコレクション、first() は 1 行(オブジェクト or null)、value('col') は 1 カラムの値のみ。

Q: chunk / chunkById の違い
A: 大量データを分割処理する際、chunk() は OFFSET ベースで重複の可能性あり。chunkById() は主キーで進めるので安全。

Q: Query Builder で N+1 は起きる?
A: Query Builder はそもそもリレーションを自動取得しないので発生しません。Eloquent でのみ with() による Eager Loading が必要です。

編集
Post Share
子ページ
  1. SELECT
  2. INSERT
  3. UPDATE
  4. DELETE
  5. order by句のキャスト
  6. count / max / average (集計)
  7. 配列を条件にする方法
  8. where句の入れ子(ネスト)
同階層のページ
  1. インストールと設定
  2. クイックスタート & チュートリアル(初心者向け)
  3. クイックスタート & チュートリアル(中級者向け)
  4. ルーティング
  5. Bladeテンプレート(ビュー/レイアウト)
  6. コントローラー
  7. マイグレーションとテーブル定義
  8. データベースの設定
  9. Eloquentモデル (ORM)
  10. SQLとクエリビルダー
  11. バリデーション
  12. .envファイルの設定値へのアクセス
  13. 動作環境による分岐処理
  14. configフォルダ配下の設定値へのアクセス
  15. assetヘルパーを利用したpublicフォルダへのアクセス
  16. storageフォルダへのアクセス
  17. アプリケーション名の変更
  18. メンテナンス
  19. ログイン画面(認証システム)の作成
  20. ログインの必須化
  21. ログインユーザー情報の取得
  22. ルートの認証化
  23. 本番サーバーへのデプロイ方法
  24. 多言語化
  25. csrf_field
  26. ファイルのダウンロード
  27. CSVのアップロードおよび読み込み(maatwebsite/excel)
  28. ページタイトルの設定
  29. コマンド一覧
  30. エラー一覧
  31. SQLの実行ログ出力方法
  32. キャッシュのクリア
  33. Selectの結果の最初もしくは最後に任意の値を追加する方法
  34. ajaxでPOST通信する際の注意点
  35. ソーシャルログインの実装
  36. セッション情報の確認
  37. ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
  38. redirectやreturn viewにメッセージを付与する方法
  39. クッキー(cookie)の設定と取得
  40. クラスの再読み込み
  41. csrfの有効時間を変更する方法
  42. ViewComposerを用いてviewに共通の値を付与する方法
  43. View::shareを用いて共通の値を各ビューに渡す方法
  44. ミドルウェアを用いた処理の共通化
  45. Middleware内でAuth::check()などを使用する方法
  46. Controller以外でリダイレクトする方法
  47. セッションの値の取得/保存/更新/削除
  48. $requestの値を変更する方法
  49. 常時SSL化
  50. ページング(ページネーション)をする方法
  51. vue.jsとの連携
  52. Vue.jsと連携するSPA実行環境構築
  53. .envの値をvue.jsで参照する方法
  54. vue.jsを本番環境にリリースする方法
  55. could not find driver(Windows, MySQL編)