1.

Laravel Query Builder の SELECT 完全ガイド

編集
この記事の要点
  • 基本: DB::table('users')->select('name', 'email')->get()
  • 生 SQL は selectRaw()、重複排除は distinct()、列追加は addSelect()
  • 取得は get() / first() / find() / value() / pluck() を場面で使い分け
  • JOIN / GROUP BY / HAVING / サブクエリ selectSub() もチェーンで書ける
  • Eloquent でも同じ感覚で User::select('name')->where(...)->get()

基本: select() で列を指定

use Illuminate\Support\Facades\DB;

// 全列
$users = DB::table('users')->get();

// 列指定
$users = DB::table('users')->select('id', 'name', 'email')->get();

// エイリアス
$users = DB::table('users')
    ->select('id', 'name AS user_name', 'email')
    ->get();

// 配列で渡す
$users = DB::table('users')->select(['id', 'name'])->get();

selectRaw: 生 SQL 式

// COUNT / SUM 等の集計
$stats = DB::table('orders')
    ->selectRaw('user_id, COUNT(*) AS cnt, SUM(amount) AS total')
    ->groupBy('user_id')
    ->get();

// 日付フォーマット
$logs = DB::table('access_logs')
    ->selectRaw("DATE_FORMAT(created_at, '%Y-%m-%d') AS day, COUNT(*) AS hits")
    ->groupBy('day')
    ->get();

// バインドパラメータを使う(SQL インジェクション対策)
$users = DB::table('users')
    ->selectRaw('price * ? AS price_with_tax', [1.1])
    ->get();

addSelect: 列を追加

$query = DB::table('users')->select('id', 'name');

if ($request->include_email) {
    $query->addSelect('email');
}

if ($request->include_role) {
    $query->addSelect('role');
}

$users = $query->get();

distinct: 重複排除

// ユニークな国一覧
$countries = DB::table('users')
    ->select('country')
    ->distinct()
    ->get();

// 複数列の組み合わせで distinct
$pairs = DB::table('orders')
    ->select('user_id', 'product_id')
    ->distinct()
    ->get();

WHERE / ORDER BY / GROUP BY / HAVING

$users = DB::table('users')
    ->select('id', 'name', 'created_at')
    ->where('active', 1)
    ->where('age', '>=', 18)
    ->whereIn('country', ['JP', 'US', 'KR'])
    ->whereNull('deleted_at')
    ->whereBetween('age', [20, 65])
    ->orderBy('created_at', 'desc')
    ->get();

// GROUP BY / HAVING
$summary = DB::table('orders')
    ->select('user_id', DB::raw('SUM(amount) AS total'))
    ->groupBy('user_id')
    ->having('total', '>', 10000)
    ->orderByDesc('total')
    ->get();

JOIN を組み合わせた SELECT

$rows = DB::table('users')
    ->join('orders', 'users.id', '=', 'orders.user_id')
    ->leftJoin('payments', 'orders.id', '=', 'payments.order_id')
    ->select(
        'users.id',
        'users.name',
        'orders.id AS order_id',
        'orders.amount',
        'payments.paid_at'
    )
    ->where('orders.status', 'completed')
    ->orderBy('orders.created_at', 'desc')
    ->get();

サブクエリ: selectSub / whereExists

// selectSub: 各 user に最終ログイン時刻を付与
$users = DB::table('users')
    ->select('id', 'name')
    ->selectSub(function ($q) {
        $q->select('created_at')
          ->from('login_logs')
          ->whereColumn('login_logs.user_id', 'users.id')
          ->orderByDesc('created_at')
          ->limit(1);
    }, 'last_login_at')
    ->get();

// whereExists: 注文を持つユーザー
$users = DB::table('users')
    ->whereExists(function ($q) {
        $q->select(DB::raw(1))
          ->from('orders')
          ->whereColumn('orders.user_id', 'users.id');
    })
    ->get();

取得メソッドの使い分け

メソッド返り値用途
get()Collection of stdClass複数行取得
first()stdClass / null1 行目を取得
find($id)stdClass / nullPK で 1 件
value('column')scalar1 列だけ取得
pluck('name')Collection単一列をリスト化
pluck('name', 'id')Collection name>キー付きリスト
count()int行数
exists()bool存在チェック(高速)
chunk(100, fn)void大量データ分割処理
cursor()Generator1 行ずつメモリ節約

pluck の便利な使い方

// id だけのリスト
$ids = DB::table('users')->where('active', 1)->pluck('id')->all();
// [1, 2, 3, 5, 8]

// id => name の連想配列(HTML select 用)
$options = DB::table('users')->pluck('name', 'id');
// [1 => 'Taro', 2 => 'Hanako', ...]

// Blade で
// 

ページネーション

$users = DB::table('users')
    ->select('id', 'name')
    ->orderBy('id')
    ->paginate(20);   // 1ページ20件

// Blade
// {{ $users->links() }}

// simplePaginate は総件数を計算せず軽量
$users = DB::table('users')->simplePaginate(20);

// カーソルベース(大量データ向け)
$users = DB::table('users')->orderBy('id')->cursorPaginate(20);

Eloquent でも同じ

use App\Models\User;

// 全列
$users = User::all();

// 列指定
$users = User::select('id', 'name', 'email')->where('active', 1)->get();

// リレーションも eager load しつつ
$users = User::with('orders:id,user_id,amount')
    ->select('id', 'name')
    ->get();

// pluck
$emails = User::where('active', 1)->pluck('email');

パフォーマンスのコツ

  • 必要な列だけ select: TEXT / JSON 列を含む全列取得は重い
  • EXPLAIN で確認: DB::table(...)->toSql() でクエリ確認 → MySQL で EXPLAIN
  • N+1 回避: Eloquent では with() で eager load
  • chunk / cursor: 10 万行以上扱うときは chunk(1000) or cursor()
  • インデックス: where / orderBy 列に index、複合 index は左端から
  • QueryBuilder の toSql() + DB::listen() でロガー

FAQ

Q: get() と first() の違い
A: get() は Collection(複数行)、first() は最初の 1 行のオブジェクト or null。「絶対1件しかないクエリ」でも get() を呼ぶと配列処理が必要になり面倒。

Q: 実行された SQL を確認したい
A: DB::table(...)->toSql() でクエリ文字列、DB::table(...)->dd() でバインド込みダンプ。DB::listen(fn($q) => Log::info($q->sql)) で全クエリログ。

Q: select で alias を付けると Eloquent で取れない
A: モデル属性として取得するには $user->getAttribute('alias') or $user->alias。生クエリの結果は stdClass なので注意。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. SELECT
  2. INSERT
  3. UPDATE
  4. DELETE
  5. order by句のキャスト
  6. count / max / average (集計)
  7. 配列を条件にする方法
  8. where句の入れ子(ネスト)