クエリビルダ
SQL を書かずに、メソッドをつないでデータを読み書きするクエリビルダの使い方を、条件の指定や並べ替え、追加・更新・削除まで一覧で説明します。
クエリビルダは、SQL(データベースに話しかける言葉)を書かずに、PHP のメソッドをつなげて問い合わせを作るしくみです。レストランで、注文の紙に「ここに丸を付ける」ように、1つずつ条件を足していくイメージです。Laravel が対応しているどのデータベースでも同じ書き方で動きます。
クエリビルダは PDO(PHP がデータベースとやりとりする部品)の値の結びつけ(バインド)を使うので、SQL インジェクション(悪い入力で SQL を書き換える攻撃)を防げます。クエリビルダに渡す値から、危ない文字を自分で取り除く必要はありません。
注意
PDO は、カラム(列)の名前を結びつけられません。使う人の入力で、問い合わせに出てくるカラムの名前(「order by」のカラムも含む)を決めさせてはいけません。
問い合わせを動かす#
表の全部の行を取る#
DB ファサード(DB::table() のように、クラス名と :: で機能を呼べる窓口)の table で、その表の問い合わせを始めます。条件をつなげ、最後に get で結果を取ります。
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show a list of all of the application's users.
*/
public function index(): View
{
$users = DB::table('users')->get();
return view('user.index', ['users' => $users]);
}
}
get は Illuminate\Support\Collection(配列を便利に扱う入れ物)を返します。中の1件ずつは、PHP の stdClass オブジェクトで、カラム名をプロパティ(オブジェクトの中の値)として読めます。
use Illuminate\Support\Facades\DB;
$users = DB::table('users')->get();
foreach ($users as $user) {
echo $user->name;
}
補足
コレクションには、データを変えたりまとめたりする強力なメソッドがたくさんあります。くわしくはコレクションのページを見てください。
1行・1つの値を取る#
1行だけ欲しいときは first を使います。stdClass オブジェクトが1つ返ります。
$user = DB::table('users')->where('name', 'John')->first();
return $user->email;
見つからないときに例外(エラー)を投げさせたいなら firstOrFail を使います。Illuminate\Database\RecordNotFoundException を受け止めないと、自動で 404 のレスポンスが返ります。
$user = DB::table('users')->where('name', 'John')->firstOrFail();
行ぜんぶは要らず、1つの値だけ欲しいときは value を使います。
$email = DB::table('users')->where('name', 'John')->value('email');
id カラムの値で1行を取るには find を使います。
$user = DB::table('users')->find(3);
1つのカラムの値の一覧を取る#
1つのカラムの値だけを集めたコレクションが欲しいときは、pluck を使います。
use Illuminate\Support\Facades\DB;
$titles = DB::table('users')->pluck('title');
foreach ($titles as $title) {
echo $title;
}
2つ目の引数に、結果のキー(見出し)にしたいカラムを書けます。
$titles = DB::table('users')->pluck('title', 'name');
foreach ($titles as $name => $title) {
echo $title;
}
少しずつ取り出す(チャンク)#
何千件もの行を扱うときは、chunk が使えます。少しずつ取り出して、クロージャ(名前のない関数)に渡します。次の例は、users を100件ずつ処理します。
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
foreach ($users as $user) {
// ...
}
});
クロージャが false を返すと、そこで止まります。
DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
// Process the records...
return false;
});
取り出しながら行を更新すると、結果が思わぬ形で変わることがあります。更新するなら chunkById を使います。主キー(行を区別する番号)にそって取り出してくれます。
DB::table('users')->where('active', false)
->chunkById(100, function (Collection $users) {
foreach ($users as $user) {
DB::table('users')
->where('id', $user->id)
->update(['active' => true]);
}
});
chunkById と lazyById は、取り出すために、問い合わせへ自分で where の条件を足します。そのため、こちらで書く条件は、クロージャでかっこにまとめておくのがふつうです。
DB::table('users')->where(function ($query) {
$query->where('credits', 1)->orWhere('credits', 2);
})->chunkById(100, function (Collection $users) {
foreach ($users as $user) {
DB::table('users')
->where('id', $user->id)
->update(['credits' => 3]);
}
});
注意
取り出しの途中で更新や削除をして、主キーや外部キーを変えると、取り出しの問い合わせに影響します。その結果、取り出されない行が出るかもしれません。
流れのように取り出す(lazy)#
lazy も少しずつ取り出しますが、クロージャに渡すのではなく、全体を1本の流れとして扱える LazyCollection(少しずつ読むコレクション)を返します。
use Illuminate\Support\Facades\DB;
DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {
// ...
});
取り出しながら更新するときは、lazyById か lazyByIdDesc を使います。主キーにそって取り出してくれます。
DB::table('users')->where('active', false)
->lazyById()->each(function (object $user) {
DB::table('users')
->where('id', $user->id)
->update(['active' => true]);
});
注意
取り出しの途中で更新や削除をして、主キーや外部キーを変えると、取り出しの問い合わせに影響します。その結果、取り出されない行が出るかもしれません。
集計する#
合計や最大などの値を取るメソッドがあります。問い合わせの最後に呼びます。
| メソッド | 求める値 |
|---|---|
count |
行の数 |
max |
最大の値 |
min |
最小の値 |
avg |
平均 |
sum |
合計 |
use Illuminate\Support\Facades\DB;
$users = DB::table('users')->count();
$price = DB::table('orders')->max('price');
ほかの条件と組み合わせられます。
$price = DB::table('orders')
->where('finalized', 1)
->avg('price');
行があるかだけ調べる#
数を数えなくても、条件に合う行があるかどうかは exists と doesntExist で分かります。
if (DB::table('orders')->where('finalized', 1)->exists()) {
// ...
}
if (DB::table('orders')->where('finalized', 1)->doesntExist()) {
// ...
}
取り出すカラムを決める(select)#
select で、取り出すカラムを選べます。as で別名も付けられます。
use Illuminate\Support\Facades\DB;
$users = DB::table('users')
->select('name', 'email as user_email')
->get();
distinct は、同じ結果の重なりをなくします。
$users = DB::table('users')->distinct()->get();
すでにある問い合わせにカラムを足すには、addSelect を使います。
$query = DB::table('users')->select('name');
$users = $query->addSelect('age')->get();
生の SQL を混ぜる(raw)#
問い合わせの中に、SQL の文字列をそのまま入れたいときは、DB::raw を使います。
$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
注意
生の文は、文字列のまま問い合わせに入ります。SQL インジェクションの穴を作らないよう、細心の注意が要ります。
raw のメソッド#
DB::raw の代わりに、問い合わせの場所ごとの専用メソッドも使えます。生の式を使った問い合わせが SQL インジェクションから守られるかは、Laravel には保証できません。
| メソッド | 入れる場所 |
|---|---|
selectRaw |
select の部分 |
whereRaw / orWhereRaw |
where の部分 |
havingRaw / orHavingRaw |
having の部分 |
orderByRaw |
order by の部分 |
groupByRaw |
group by の部分 |
selectRaw は、addSelect(DB::raw(/* ... */)) の代わりに使えます。2つ目の引数に、結びつける値の配列を渡せます。
$orders = DB::table('orders')
->selectRaw('price * ? as price_with_tax', [1.0825])
->get();
whereRaw と orWhereRaw も、2つ目の引数に結びつける値を渡せます。
$orders = DB::table('orders')
->whereRaw('price > IF(state = "TX", ?, 100)', [200])
->get();
havingRaw と orHavingRaw も同じです。
$orders = DB::table('orders')
->select('department', DB::raw('SUM(price) as total_sales'))
->groupBy('department')
->havingRaw('SUM(price) > ?', [2500])
->get();
$orders = DB::table('orders')
->orderByRaw('updated_at - created_at DESC')
->get();
$orders = DB::table('orders')
->select('city', 'state')
->groupByRaw('city, state')
->get();
表をつなげる(join)#
join は、ほかの表の行を横にくっつけて取り出す方法です。
| メソッド | 働き |
|---|---|
join |
内部結合(両方に相手がある行だけ) |
leftJoin |
左外部結合(左の表の行は全部残す) |
rightJoin |
右外部結合(右の表の行は全部残す) |
crossJoin |
交差結合(全部の組み合わせ) |
joinSub / leftJoinSub / rightJoinSub |
サブクエリ(問い合わせの中の問い合わせ)とつなぐ |
joinLateral / leftJoinLateral |
行ごとに動くサブクエリとつなぐ |
内部結合#
join の1つ目の引数は、つなぐ表の名前です。残りの引数は、つなぐ条件のカラムです。複数の表を続けてつなげます。
use Illuminate\Support\Facades\DB;
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();
左外部結合・右外部結合#
leftJoin と rightJoin は、join と同じ引数で使います。
$users = DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->get();
$users = DB::table('users')
->rightJoin('posts', 'users.id', '=', 'posts.user_id')
->get();
交差結合#
crossJoin は、最初の表と、つなぐ表の、すべての組み合わせ(直積)を作ります。
$sizes = DB::table('sizes')
->crossJoin('colors')
->get();
くわしい join の条件#
join の2つ目の引数にクロージャを渡すと、Illuminate\Database\Query\JoinClause が渡され、join の条件をくわしく書けます。
DB::table('users')
->join('contacts', function (JoinClause $join) {
$join->on('users.id', '=', 'contacts.user_id')->orOn(/* ... */);
})
->get();
JoinClause の where と orWhere を使うと、2つのカラムの比べっこではなく、カラムと値を比べられます。
DB::table('users')
->join('contacts', function (JoinClause $join) {
$join->on('users.id', '=', 'contacts.user_id')
->where('contacts.user_id', '>', 5);
})
->get();
サブクエリとつなぐ#
joinSub・leftJoinSub・rightJoinSub は、問い合わせをサブクエリとつなぎます。引数は3つで、サブクエリ、その別名、つなぐカラムを決めるクロージャです。次の例は、各ユーザーに、公開した最新の記事の created_at を付けて取り出します。
$latestPosts = DB::table('posts')
->select('user_id', DB::raw('MAX(created_at) as last_post_created_at'))
->where('is_published', true)
->groupBy('user_id');
$users = DB::table('users')
->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) {
$join->on('users.id', '=', 'latest_posts.user_id');
})->get();
行ごとに動かすサブクエリとつなぐ(lateral join)#
注意
lateral join に対応しているのは、PostgreSQL、MySQL 8.0.14 以上、SQL Server です。
joinLateral と leftJoinLateral は、サブクエリと「lateral join」でつなぎます。引数は2つで、サブクエリとその別名です。つなぐ条件は、サブクエリの中の where に書きます。lateral join では、サブクエリが外の表の1行ごとに動きます。そのため、サブクエリの中から外の表のカラムを使えます。
次の例は、ユーザーと、そのユーザーの最新の記事3本を取り出します。1人のユーザーから、最大3行の結果ができます。つなぐ条件は、サブクエリの中の whereColumn で、いまのユーザーの行を指しています。
$latestPosts = DB::table('posts')
->select('id as post_id', 'title as post_title', 'created_at as post_created_at')
->whereColumn('user_id', 'users.id')
->orderBy('created_at', 'desc')
->limit(3);
$users = DB::table('users')
->joinLateral($latestPosts, 'latest_posts')
->get();
問い合わせを合わせる(union)#
union は、2つ以上の問い合わせの結果を1つにまとめます。
use Illuminate\Support\Facades\DB;
$usersWithoutFirstName = DB::table('users')
->whereNull('first_name');
$users = DB::table('users')
->whereNull('last_name')
->union($usersWithoutFirstName)
->get();
unionAll は、重なった結果を取り除きません。使い方は union と同じです。
条件を付ける(where)#
where の基本#
where は、問い合わせに条件を足します。いちばん基本の形は3つの引数です。カラムの名前、演算子(比べる記号)、比べる値です。演算子は、データベースが対応しているものなら使えます。
次の例は、votes が 100 で、かつ age が 35 より大きいユーザーを取ります。
$users = DB::table('users')
->where('votes', '=', 100)
->where('age', '>', 35)
->get();
= で比べるときは、演算子を省いて、2つ目の引数に値を書けます。
$users = DB::table('users')->where('votes', 100)->get();
連想配列(名前と値の組の並び)を渡すと、複数のカラムをまとめて比べられます。
$users = DB::table('users')->where([
'first_name' => 'Jane',
'last_name' => 'Doe',
])->get();
いろいろな演算子が使えます。
$users = DB::table('users')
->where('votes', '>=', 100)
->get();
$users = DB::table('users')
->where('votes', '<>', 100)
->get();
$users = DB::table('users')
->where('name', 'like', 'T%')
->get();
3つの引数の配列を並べた配列も渡せます。
$users = DB::table('users')->where([
['status', '=', '1'],
['subscribed', '<>', '1'],
])->get();
注意
PDO は、カラムの名前を結びつけられません。使う人の入力で、問い合わせに出てくるカラムの名前(「order by」のカラムも含む)を決めさせてはいけません。
注意
MySQL と MariaDB は、文字と数を比べるとき、文字を自動で整数に変えます。数でない文字は 0 になるため、思わぬ結果になることがあります。たとえば、secret カラムの値が aaa の行は、User::where('secret', 0) で取り出されてしまいます。これを防ぐため、問い合わせに使う前に、値を正しい型に変えておきます。
or の条件(orWhere)#
where を続けると、条件は and(かつ)でつながります。or(または)でつなぐには orWhere を使います。引数は where と同じです。
$users = DB::table('users')
->where('votes', '>', 100)
->orWhere('name', 'John')
->get();
or の条件をかっこでまとめたいときは、orWhere の1つ目の引数にクロージャを渡します。
use Illuminate\Database\Query\Builder;
$users = DB::table('users')
->where('votes', '>', 100)
->orWhere(function (Builder $query) {
$query->where('name', 'Abigail')
->where('votes', '>', 50);
})
->get();
この例からは、次の SQL ができます。
select * from users where votes > 100 or (name = 'Abigail' and votes > 50)
注意
グローバルスコープ(モデルに自動で付く条件)が使われたときに思わぬ動きをしないよう、orWhere は必ずかっこでまとめます。
当てはまらないものを選ぶ(whereNot)#
whereNot と orWhereNot は、条件のまとまりを「ではない」にひっくり返します。次の例は、特売品、または値段が10未満の商品を、結果から除きます。
$products = DB::table('products')
->whereNot(function (Builder $query) {
$query->where('clearance', true)
->orWhere('price', '<', 10);
})
->get();
複数のカラムをまとめて比べる(whereAny / whereAll / whereNone)#
同じ条件を、複数のカラムに当てはめたいことがあります。
| メソッド | 取り出す行 |
|---|---|
whereAny |
並べたカラムのどれか1つでも条件に合う行 |
whereAll |
並べたカラムの全部が条件に合う行 |
whereNone |
並べたカラムのどれも条件に合わない行 |
whereAny の例です。
$users = DB::table('users')
->where('active', true)
->whereAny([
'name',
'email',
'phone',
], 'like', 'Example%')
->get();
できる SQL はこうです。
SELECT *
FROM users
WHERE active = true AND (
name LIKE 'Example%' OR
email LIKE 'Example%' OR
phone LIKE 'Example%'
)
whereAll の例です。
$posts = DB::table('posts')
->where('published', true)
->whereAll([
'title',
'content',
], 'like', '%Laravel%')
->get();
SELECT *
FROM posts
WHERE published = true AND (
title LIKE '%Laravel%' AND
content LIKE '%Laravel%'
)
whereNone の例です。
$albums = DB::table('albums')
->where('published', true)
->whereNone([
'title',
'lyrics',
'tags',
], 'like', '%explicit%')
->get();
SELECT *
FROM albums
WHERE published = true AND NOT (
title LIKE '%explicit%' OR
lyrics LIKE '%explicit%' OR
tags LIKE '%explicit%'
)
JSON のカラムを調べる#
JSON カラム(JSON という形式で値をしまうカラム)に対応しているデータベースでは、JSON の中も調べられます。対応するのは、MariaDB 10.3 以上、MySQL 8.0 以上、PostgreSQL 12.0 以上、SQL Server 2017 以上、SQLite 3.39.0 以上です。JSON の中の値には -> でたどります。
$users = DB::table('users')
->where('preferences->dining->meal', 'salad')
->get();
whereIn も使えます。
$users = DB::table('users')
->whereIn('preferences->dining->meal', ['pasta', 'salad', 'sandwiches'])
->get();
JSON のメソッドは次のとおりです。
| メソッド | 働き |
|---|---|
whereJsonContains |
JSON の配列に、その値が入っている行 |
whereJsonDoesntContain |
JSON の配列に、その値が入っていない行 |
whereJsonContainsKey |
JSON にそのキーがある行 |
whereJsonDoesntContainKey |
JSON にそのキーがない行 |
whereJsonLength |
JSON の配列の長さで比べる |
$users = DB::table('users')
->whereJsonContains('options->languages', 'en')
->get();
$users = DB::table('users')
->whereJsonDoesntContain('options->languages', 'en')
->get();
MariaDB・MySQL・PostgreSQL では、whereJsonContains と whereJsonDoesntContain に値の配列も渡せます。
$users = DB::table('users')
->whereJsonContains('options->languages', ['en', 'de'])
->get();
$users = DB::table('users')
->whereJsonDoesntContain('options->languages', ['en', 'de'])
->get();
$users = DB::table('users')
->whereJsonContainsKey('preferences->dietary_requirements')
->get();
$users = DB::table('users')
->whereJsonDoesntContainKey('preferences->dietary_requirements')
->get();
$users = DB::table('users')
->whereJsonLength('options->languages', 0)
->get();
$users = DB::table('users')
->whereJsonLength('options->languages', '>', 1)
->get();
そのほかの where#
ここからは、よく使う where の仲間を、種類ごとに見ていきます。
| メソッド | 働き |
|---|---|
whereLike / orWhereLike / whereNotLike / orWhereNotLike |
パターンに合う(合わない)文字を探す |
whereIn / whereNotIn / orWhereIn / orWhereNotIn |
配列の中にある(ない)値 |
whereBetween / orWhereBetween |
2つの値の間にある |
whereNotBetween / orWhereNotBetween |
2つの値の間にない |
whereBetweenColumns / whereNotBetweenColumns / orWhereBetweenColumns / orWhereNotBetweenColumns |
同じ行の2つのカラムの値の間にある(ない) |
whereValueBetween / whereValueNotBetween / orWhereValueBetween / orWhereValueNotBetween |
ある値が、同じ行の2つのカラムの間にある(ない) |
whereNull / whereNotNull / orWhereNull / orWhereNotNull |
値が NULL(空っぽ)である(でない) |
whereNullSafeEquals / orWhereNullSafeEquals |
NULL どうしも等しいとして比べる |
whereDate / whereMonth / whereDay / whereYear / whereTime |
日付・月・日・年・時刻で比べる |
wherePast / whereFuture / whereNowOrPast / whereNowOrFuture |
過去・未来(いまを含む場合もある)の日時 |
whereToday / whereBeforeToday / whereAfterToday / whereTodayOrBefore / whereTodayOrAfter |
今日・今日より前・今日より後(今日を含む場合もある) |
whereColumn / orWhereColumn |
2つのカラムの値を比べる |
whereLike など#
whereLike は、パターンに合う文字を探します(LIKE)。データベースの種類によらず同じように書け、大文字・小文字を区別するかも選べます。既定では、区別しません。
$users = DB::table('users')
->whereLike('name', '%John%')
->get();
caseSensitive を付けると、大文字・小文字を区別します。
$users = DB::table('users')
->whereLike('name', '%John%', caseSensitive: true)
->get();
orWhereLike は、LIKE の条件を or で足します。
$users = DB::table('users')
->where('votes', '>', 100)
->orWhereLike('name', '%John%')
->get();
whereNotLike は、NOT LIKE の条件を足します。
$users = DB::table('users')
->whereNotLike('name', '%John%')
->get();
orWhereNotLike は、NOT LIKE の条件を or で足します。
$users = DB::table('users')
->where('votes', '>', 100)
->orWhereNotLike('name', '%John%')
->get();
注意
whereLike の、大文字・小文字を区別する指定は、いまのところ SQL Server では使えません。
whereIn など#
whereIn は、カラムの値が、配列の中にあるかを調べます。
$users = DB::table('users')
->whereIn('id', [1, 2, 3])
->get();
whereNotIn は、配列の中にないかを調べます。
$users = DB::table('users')
->whereNotIn('id', [1, 2, 3])
->get();
2つ目の引数に、問い合わせ(クエリオブジェクト)も渡せます。
$activeUsers = DB::table('users')->select('id')->where('is_active', 1);
$comments = DB::table('comments')
->whereIn('user_id', $activeUsers)
->get();
できる SQL はこうです。
select * from comments where user_id in (
select id
from users
where is_active = 1
)
注意
整数の大きな配列を問い合わせに入れるときは、whereIntegerInRaw か whereIntegerNotInRaw を使うと、メモリの使い方を大きく減らせます。
whereBetween など#
whereBetween は、カラムの値が2つの値の間にあるかを調べます。
$users = DB::table('users')
->whereBetween('votes', [1, 100])
->get();
whereNotBetween は、2つの値の外にあるかを調べます。
$users = DB::table('users')
->whereNotBetween('votes', [1, 100])
->get();
whereBetweenColumns は、カラムの値が、同じ行の2つのカラムの値の間にあるかを調べます。
$patients = DB::table('patients')
->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])
->get();
whereNotBetweenColumns は、その外にあるかを調べます。
$patients = DB::table('patients')
->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])
->get();
whereValueBetween は、与えた値が、同じ行にある同じ型の2つのカラムの値の間にあるかを調べます。
$products = DB::table('products')
->whereValueBetween(100, ['min_price', 'max_price'])
->get();
whereValueNotBetween は、その外にあるかを調べます。
$products = DB::table('products')
->whereValueNotBetween(100, ['min_price', 'max_price'])
->get();
whereNull など#
whereNull は、カラムの値が NULL(値が入っていない)かを調べます。
$users = DB::table('users')
->whereNull('updated_at')
->get();
whereNotNull は、NULL でないかを調べます。
$users = DB::table('users')
->whereNotNull('updated_at')
->get();
whereNullSafeEquals と orWhereNullSafeEquals は、NULL どうしも等しいとみなして比べます。
$lastLoginIp = $request->input('last_login_ip');
$users = DB::table('users')
->whereNullSafeEquals('last_login_ip', $lastLoginIp)
->get();
whereDate など#
whereDate は、カラムの値を日付と比べます。
$users = DB::table('users')
->whereDate('created_at', '2016-12-31')
->get();
whereMonth は月と比べます。
$users = DB::table('users')
->whereMonth('created_at', '12')
->get();
whereDay は、月の中の日と比べます。
$users = DB::table('users')
->whereDay('created_at', '31')
->get();
whereYear は年と比べます。
$users = DB::table('users')
->whereYear('created_at', '2016')
->get();
whereTime は時刻と比べます。
$users = DB::table('users')
->whereTime('created_at', '=', '11:20:45')
->get();
wherePast など#
wherePast と whereFuture は、カラムの値が過去か未来かを調べます。
$invoices = DB::table('invoices')
->wherePast('due_at')
->get();
$invoices = DB::table('invoices')
->whereFuture('due_at')
->get();
whereNowOrPast と whereNowOrFuture は、いまの日時も含めて、過去か未来かを調べます。
$invoices = DB::table('invoices')
->whereNowOrPast('due_at')
->get();
$invoices = DB::table('invoices')
->whereNowOrFuture('due_at')
->get();
whereToday・whereBeforeToday・whereAfterToday は、それぞれ今日・今日より前・今日より後かを調べます。
$invoices = DB::table('invoices')
->whereToday('due_at')
->get();
$invoices = DB::table('invoices')
->whereBeforeToday('due_at')
->get();
$invoices = DB::table('invoices')
->whereAfterToday('due_at')
->get();
whereTodayOrBefore と whereTodayOrAfter は、今日を含めて、前か後かを調べます。
$invoices = DB::table('invoices')
->whereTodayOrBefore('due_at')
->get();
$invoices = DB::table('invoices')
->whereTodayOrAfter('due_at')
->get();
whereColumn#
whereColumn は、2つのカラムの値が等しいかを調べます。
$users = DB::table('users')
->whereColumn('first_name', 'last_name')
->get();
比べる演算子も渡せます。
$users = DB::table('users')
->whereColumn('updated_at', '>', 'created_at')
->get();
比べるものの配列も渡せます。条件は and でつながります。
$users = DB::table('users')
->whereColumn([
['first_name', '=', 'last_name'],
['updated_at', '>', 'created_at'],
])->get();
条件をかっこでまとめる#
いくつかの where を、かっこでまとめたいときがあります。とくに orWhere は、いつもかっこでまとめるのがよいやり方です。where にクロージャを渡すと、かっこのまとまりを作れます。
$users = DB::table('users')
->where('name', '=', 'John')
->where(function (Builder $query) {
$query->where('votes', '>', 100)
->orWhere('title', '=', 'Admin');
})
->get();
クロージャには、問い合わせが渡され、かっこの中の条件をそこに書きます。できる SQL はこうです。
select * from users where name = 'John' and (votes > 100 or title = 'Admin')
注意
グローバルスコープが使われたときに思わぬ動きをしないよう、orWhere は必ずかっこでまとめます。
くわしい where#
行があるかで選ぶ(whereExists)#
whereExists は、SQL の「where exists」(別の問い合わせに当てはまる行があるか)の条件を作ります。クロージャには問い合わせが渡されます。そこに、exists のかっこの中に入れる問い合わせを書きます。
$users = DB::table('users')
->whereExists(function (Builder $query) {
$query->select(DB::raw(1))
->from('orders')
->whereColumn('orders.user_id', 'users.id');
})
->get();
クロージャの代わりに、問い合わせのオブジェクトも渡せます。
$orders = DB::table('orders')
->select(DB::raw(1))
->whereColumn('orders.user_id', 'users.id');
$users = DB::table('users')
->whereExists($orders)
->get();
どちらの例も、同じ SQL になります。
select * from users
where exists (
select 1
from orders
where orders.user_id = users.id
)
サブクエリで比べる#
サブクエリの結果を、ある値と比べたいことがあります。where に、クロージャと値を渡します。次の例は、直近の「membership」の種類が Pro のユーザーを取ります。
use App\Models\User;
use Illuminate\Database\Query\Builder;
$users = User::where(function (Builder $query) {
$query->select('type')
->from('membership')
->whereColumn('membership.user_id', 'users.id')
->orderByDesc('membership.start_date')
->limit(1);
}, 'Pro')->get();
カラムの値を、サブクエリの結果と比べたいときは、カラム、演算子、クロージャを渡します。次の例は、金額が平均より小さい収入の記録を取ります。
use App\Models\Income;
use Illuminate\Database\Query\Builder;
$incomes = Income::where('amount', '<', function (Builder $query) {
$query->selectRaw('avg(i.amount)')->from('incomes as i');
})->get();
全文検索(whereFullText)#
注意
全文検索の where に対応しているのは、いまのところ MariaDB、MySQL、PostgreSQL です。
whereFullText と orWhereFullText は、全文インデックス(文章の中の言葉を速く探すための目印)を付けたカラムを、全文検索します。Laravel が、データベースに合う SQL に変えます。たとえば MariaDB や MySQL では、MATCH AGAINST が作られます。
$users = DB::table('users')
->whereFullText('bio', 'web developer')
->get();
ベクトルの近さで選ぶ#
補足
ベクトルの近さを使う where に対応しているのは、pgvector 拡張を使った PostgreSQL と、MariaDB 11.7 以上です。ベクトルのカラムとインデックスの作り方は、マイグレーションのページにあります。
ベクトルは、文章や画像の意味を数の並びにしたものです。whereVectorSimilarTo は、与えたベクトルとのコサイン類似度(向きの近さ)で行を選び、近い順に並べます。minSimilarity の値は 0.0 から 1.0 で、1.0 が同じものです。
$documents = DB::table('documents')
->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
->limit(10)
->get();
ベクトルの代わりに文字列を渡すと、Laravel AI SDK を使って、自動でその文字列のベクトル(埋め込み)を作ります。
$documents = DB::table('documents')
->whereVectorSimilarTo('embedding', 'Best wineries in Napa Valley')
->limit(10)
->get();
既定では、近い順(いちばん似ているものが先)に並びます。order に false を渡すと、この並べ替えをやめられます。
$documents = DB::table('documents')
->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4, order: false)
->orderBy('created_at', 'desc')
->limit(10)
->get();
細かく決めたいときは、次の3つのメソッドを別々に使えます。
| メソッド | 働き |
|---|---|
selectVectorDistance |
ベクトルの距離を、取り出す値に加える |
whereVectorDistanceLessThan |
距離が、決めた値より小さい行を選ぶ |
orderByVectorDistance |
距離の順に並べる |
$documents = DB::table('documents')
->select('*')
->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
->orderByVectorDistance('embedding', $queryEmbedding)
->limit(10)
->get();
PostgreSQL では、vector のカラムを作る前に、pgvector 拡張を読み込んでおく必要があります。
Schema::ensureVectorExtensionExists();
並べ替え・グループ・件数の制限#
並べ替え(orderBy)#
orderBy は、カラムの値で結果を並べ替えます。1つ目の引数が並べ替えるカラム、2つ目が向きで、asc(小さい順)か desc(大きい順)です。
$users = DB::table('users')
->orderBy('name', 'desc')
->get();
複数のカラムで並べ替えるときは、orderBy を必要なだけ続けます。
$users = DB::table('users')
->orderBy('name', 'desc')
->orderBy('email', 'asc')
->get();
向きは省けて、既定は小さい順です。大きい順にするには、2つ目の引数に desc を書くか、orderByDesc を使います。
$users = DB::table('users')
->orderByDesc('verified_at')
->get();
-> を使うと、JSON カラムの中の値でも並べ替えられます。
$corporations = DB::table('corporations')
->where('country', 'US')
->orderBy('location->state')
->get();
新しい順・古い順(latest / oldest)#
latest と oldest は、日付で並べ替えます。既定では、表の created_at カラムを使います。別のカラム名も渡せます。
$user = DB::table('users')
->latest()
->first();
ランダムに並べる#
inRandomOrder は、結果をランダムに並べます。たとえば、ランダムにユーザーを1人取るのに使えます。
$randomUser = DB::table('users')
->inRandomOrder()
->first();
並べ替えを取り消す・変える#
reorder は、それまでに付けた「order by」を全部取り除きます。
$query = DB::table('users')->orderBy('name');
$unorderedUsers = $query->reorder()->get();
カラムと向きを渡すと、前の並べ替えを全部取り除いて、新しい並べ替えにします。
$query = DB::table('users')->orderBy('name');
$usersOrderedByEmail = $query->reorder('email', 'desc')->get();
大きい順にするなら、reorderDesc が使えます。
$query = DB::table('users')->orderBy('name');
$usersOrderedByEmail = $query->reorderDesc('email')->get();
グループにまとめる(groupBy / having)#
groupBy と having で、結果をグループにまとめられます。having の引数は、where と似ています。
$users = DB::table('users')
->groupBy('account_id')
->having('account_id', '>', 100)
->get();
havingBetween は、ある範囲の結果だけを選びます。
$report = DB::table('orders')
->selectRaw('count(id) as number_of_orders, customer_id')
->groupBy('customer_id')
->havingBetween('number_of_orders', [5, 15])
->get();
groupBy に複数の引数を渡すと、複数のカラムでまとめられます。
$users = DB::table('users')
->groupBy('first_name', 'status')
->having('account_id', '>', 100)
->get();
もっと複雑な having は、前に出てきた havingRaw を使います。
件数を制限する(limit / offset)#
limit は、取り出す行の数を決め、offset は、最初の何行を飛ばすかを決めます。
$users = DB::table('users')
->offset(10)
->limit(5)
->get();
条件によって付けたり付けなかったりする(when)#
別の条件しだいで、問い合わせの一部を付けたいことがあります。たとえば、届いたリクエストに値があるときだけ where を足したい場合です。when を使います。
$role = $request->input('role');
$users = DB::table('users')
->when($role, function (Builder $query, string $role) {
$query->where('role_id', $role);
})
->get();
when は、1つ目の引数が true のときだけクロージャを動かします。false のときは動かしません。上の例では、role がリクエストにあり、true として扱えるときだけ、クロージャが動きます。
3つ目の引数にもう1つクロージャを渡すと、1つ目が false のときだけ動きます。次の例は、並べ方の既定を決めるのに使っています。
$sortByVotes = $request->boolean('sort_by_votes');
$users = DB::table('users')
->when($sortByVotes, function (Builder $query, bool $sortByVotes) {
$query->orderBy('votes');
}, function (Builder $query) {
$query->orderBy('name');
})
->get();
データを追加する(insert)#
insert は、表に行を追加します。カラム名と値の配列を渡します。
DB::table('users')->insert([
'email' => 'kayla@example.com',
'votes' => 0
]);
配列の配列を渡すと、いくつもの行をまとめて追加できます。
DB::table('users')->insert([
['email' => 'picard@example.com', 'votes' => 0],
['email' => 'janeway@example.com', 'votes' => 0],
]);
追加の仲間のメソッドは次のとおりです。
| メソッド | 働き |
|---|---|
insert |
行を追加する |
insertOrIgnore |
追加するときのエラーを無視する |
insertUsing |
サブクエリの結果を追加する |
insertGetId |
追加して、自動でふられた ID を受け取る |
upsert |
なければ追加し、あれば更新する |
insertOrIgnore は、追加するときのエラーを無視します。重なった行のエラーが無視されるほか、データベースの種類によっては、ほかの種類のエラーも無視されるかもしれません。たとえば、MySQL の厳格モード(strict mode。おかしな値を入れようとするとエラーにする設定)も素通りしてしまいます。
DB::table('users')->insertOrIgnore([
['id' => 1, 'email' => 'sisko@example.com'],
['id' => 2, 'email' => 'archer@example.com'],
]);
insertUsing は、サブクエリで選んだデータを、新しい行として追加します。
DB::table('pruned_users')->insertUsing([
'id', 'name', 'email', 'email_verified_at'
], DB::table('users')->select(
'id', 'name', 'email', 'email_verified_at'
)->where('updated_at', '<=', now()->minus(months: 1)));
自動でふられる ID を受け取る#
表に、自動で増える ID があるなら、insertGetId で、追加して、その ID を受け取れます。
$id = DB::table('users')->insertGetId(
['email' => 'john@example.com', 'votes' => 0]
);
注意
PostgreSQL では、insertGetId は、自動で増えるカラムの名前が id であると考えます。別の「シーケンス」(番号を出す仕組み)から ID を取りたいときは、2つ目の引数にカラム名を渡します。
なければ追加・あれば更新(upsert)#
upsert は、まだない行は追加し、すでにある行は新しい値で更新します。引数は3つです。1つ目は、追加または更新する値です。2つ目は、行を1つに見分けるためのカラムです。3つ目は、行がすでにあったときに更新するカラムの配列です。
DB::table('flights')->upsert(
[
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
],
['departure', 'destination'],
['price']
);
この例では、2つの行の追加を試します。departure と destination が同じ行がすでにあれば、その行の price を更新します。
注意
SQL Server 以外のデータベースでは、upsert の2つ目の引数のカラムに、「primary」か「unique」のインデックスが必要です。また、MariaDB と MySQL のドライバーは、2つ目の引数を無視して、表の「primary」と「unique」のインデックスで、すでにある行かを見分けます。
データを更新する(update)#
update は、すでにある行を更新します。insert と同じく、カラムと値の組を渡します。更新した行の数が返ります。where で更新する行を絞れます。
$affected = DB::table('users')
->where('id', 1)
->update(['votes' => 1]);
あれば更新・なければ追加(updateOrInsert)#
「あれば更新し、なければ追加したい」ときは、updateOrInsert を使います。引数は2つで、行を探す条件の配列と、更新するカラムと値の配列です。
まず1つ目の引数の条件で行を探します。あれば、2つ目の値で更新します。なければ、2つの配列を合わせた値で、新しい行を追加します。
DB::table('users')
->updateOrInsert(
['email' => 'john@example.com', 'name' => 'John'],
['votes' => '2']
);
クロージャを渡すと、行があるかどうかで、更新や追加する値を変えられます。
DB::table('users')->updateOrInsert(
['user_id' => $user_id],
fn ($exists) => $exists ? [
'name' => $data['name'],
'email' => $data['email'],
] : [
'name' => $data['name'],
'email' => $data['email'],
'marketable' => true,
],
);
JSON のカラムを更新する#
JSON のカラムを更新するときは、-> で、JSON の中の更新したいキーを指します。この操作に対応しているのは、MariaDB 10.3 以上、MySQL 5.7 以上、PostgreSQL 9.5 以上です。
$affected = DB::table('users')
->where('id', 1)
->update(['options->enabled' => true]);
増やす・減らす(increment / decrement)#
カラムの値を増やしたり減らしたりする便利なメソッドがあります。どちらも、1つ目の引数に変えるカラムを渡し、2つ目の引数で増減の量を決められます。
| メソッド | 働き |
|---|---|
increment |
値を増やす |
decrement |
値を減らす |
incrementEach |
複数のカラムをまとめて増やす |
decrementEach |
複数のカラムをまとめて減らす |
DB::table('users')->increment('votes');
DB::table('users')->increment('votes', 5);
DB::table('users')->decrement('votes');
DB::table('users')->decrement('votes', 5);
3つ目の引数に配列を渡すと、増やすついでに、別のカラムも更新できます。
DB::table('users')->increment('votes', 1, ['name' => 'John']);
incrementEach と decrementEach は、複数のカラムをまとめて増やしたり減らしたりします。
DB::table('users')->incrementEach([
'votes' => 5,
'balance' => 100,
]);
データを消す(delete)#
delete は、表から行を消します。消した行の数が返ります。delete の前に where を付ければ、消す行を絞れます。
$deleted = DB::table('users')->delete();
$deleted = DB::table('users')->where('votes', '>', 100)->delete();
悲観的ロック#
悲観的ロックは、読んだ行に鍵(ロック)をかけて、ほかの処理に書き換えられないようにする方法です。「だれかが書き換えるかもしれない」と先回りして守るので、悲観的と呼びます。select のときに使えるメソッドが2つあります。
| メソッド | 働き |
|---|---|
sharedLock |
共有ロック。トランザクションが終わるまで、読んだ行を書き換えられないようにする |
lockForUpdate |
更新用のロック。読んだ行を書き換えられず、ほかの共有ロックで読まれることもないようにする |
DB::table('users')
->where('votes', '>', 100)
->sharedLock()
->get();
DB::table('users')
->where('votes', '>', 100)
->lockForUpdate()
->get();
必須ではありませんが、悲観的ロックはトランザクションの中で使うのがすすめられています。処理が終わるまで、取り出したデータが書き換えられないようにするためです。失敗したときは、トランザクションが変更を元に戻し、ロックも自動で外します。
DB::transaction(function () {
$sender = DB::table('users')
->lockForUpdate()
->find(1);
$receiver = DB::table('users')
->lockForUpdate()
->find(2);
if ($sender->balance < 100) {
throw new RuntimeException('Balance too low.');
}
DB::table('users')
->where('id', $sender->id)
->update([
'balance' => $sender->balance - 100
]);
DB::table('users')
->where('id', $receiver->id)
->update([
'balance' => $receiver->balance + 100
]);
});
問い合わせの部品を使い回す#
アプリの中で同じ問い合わせの書き方がくり返し出てくるときは、tap と pipe で、その部分をオブジェクトに取り出せます。たとえば、次の2つの問い合わせがあるとします。
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
$destination = $request->query('destination');
DB::table('flights')
->when($destination, function (Builder $query, string $destination) {
$query->where('destination', $destination);
})
->orderByDesc('price')
->get();
// ...
$destination = $request->query('destination');
DB::table('flights')
->when($destination, function (Builder $query, string $destination) {
$query->where('destination', $destination);
})
->where('user', $request->user()->id)
->orderBy('destination')
->get();
2つに共通する、行き先で絞る部分を、使い回せるオブジェクトにします。
<?php
namespace App\Scopes;
use Illuminate\Database\Query\Builder;
class DestinationFilter
{
public function __construct(
private ?string $destination,
) {
//
}
public function __invoke(Builder $query): void
{
$query->when($this->destination, function (Builder $query) {
$query->where('destination', $this->destination);
});
}
}
tap を使えば、そのオブジェクトの処理を、問い合わせに当てはめられます。
use App\Scopes\DestinationFilter;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
DB::table('flights')
->tap(new DestinationFilter($destination))
->orderByDesc('price')
->get();
// ...
DB::table('flights')
->tap(new DestinationFilter($destination))
->where('user', $request->user()->id)
->orderBy('destination')
->get();
pipe#
tap は、いつもクエリビルダを返します。問い合わせを動かして、別の値を返すオブジェクトを取り出したいときは、pipe を使います。
次の Paginate は、アプリのあちこちで使う、ページ送りの処理をまとめたものです。DestinationFilter が問い合わせに条件を足すだけなのとちがい、Paginate は問い合わせを動かし、ページ送りのオブジェクト(paginator)を返します。
<?php
namespace App\Scopes;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Query\Builder;
class Paginate
{
public function __construct(
private string $sortBy = 'timestamp',
private string $sortDirection = 'desc',
private int $perPage = 25,
) {
//
}
public function __invoke(Builder $query): LengthAwarePaginator
{
return $query->orderBy($this->sortBy, $this->sortDirection)
->paginate($this->perPage, pageName: 'p');
}
}
pipe を使って、この共通のページ送りの処理を当てはめられます。
$flights = DB::table('flights')
->tap(new DestinationFilter($destination))
->pipe(new Paginate);
中身を見る(デバッグ)#
問い合わせを作っている途中で、dd と dump を使うと、いまの問い合わせの SQL と結びつけた値を見られます。dd は表示したあと、処理を止めます。dump は表示して、処理を続けます。
DB::table('users')->where('votes', '>', 100)->dd();
DB::table('users')->where('votes', '>', 100)->dump();
dumpRawSql と ddRawSql は、結びつけた値を SQL の中に埋め込んだ形で見せます。
DB::table('users')->where('votes', '>', 100)->dumpRawSql();
DB::table('users')->where('votes', '>', 100)->ddRawSql();
関連するページ#
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。