LaravelのwhereNotメソッドを活用した効率的なクエリの書き方と実践例

基本文法・構文ガイド

whereNot()は、指定した条件に一致しないレコードを取得するLaravelのクエリメソッドです。たとえば「ステータスが’active’以外のユーザーを取得したい」という場面で、->whereNot('status', 'active')と書くだけで簡潔に表現できます。

この記事では、whereNot()の基本から、where()/whereNotIn()/doesntHave()との使い分け、EloquentとQuery Builderでの実例、SQLへの変換イメージ、よくある注意点まで体系的に解説します。複数の値を除外するwhereNotIn()の構文だけをすぐに知りたい方は、後述の「whereNotInの使い方」の節、またはwhereNotIn — 指定キーの値が集合に含まれない要素だけ残すを先にご覧ください。

whereNot()メソッドとは?

LaravelのwhereNotメソッドは、クエリビルダを利用する際に、特定の条件に一致しないレコードを取得するために使用されます。標準のwhereメソッドが条件に一致するレコードをフィルタリングするのに対し、whereNotメソッドは、その逆の操作を行います。特定の条件を除外してデータを取得したい場合に非常に便利です。

whereNot()はLaravel 8.57以降で使用できます。それ以前のバージョンではwhere('column', '!=', 'value')で代替できます。

where() / whereNot() / whereNotIn() / doesntHave() の使い分け

否定条件を書く際に選択肢が複数あります。以下の表で用途を整理します。

メソッド 用途 生成されるSQL
where('col', 'val') カラムが値と等しいレコードを取得 WHERE col = 'val'
whereNot('col', 'val') カラムが値と等しくないレコードを取得 WHERE NOT (col = 'val')
where('col', '!=', 'val') whereNotと同等(互換性が高い) WHERE col != 'val'
whereNotIn('col', [...]) カラムが配列内の値のどれにも一致しない WHERE col NOT IN (...)
doesntHave('relation') リレーションが存在しないレコードを取得 WHERE NOT EXISTS (...)

選択の目安:

  • 単一値の除外 → whereNot()
  • 複数値の除外 → whereNotIn()
  • リレーション有無での絞り込み → doesntHave()

「値が存在するかどうか」だけを判定したい場合は、否定条件よりもLaravel Eloquent Existsメソッドの使い方とパフォーマンス向上の秘訣で紹介しているexistsメソッドの方が意図が明確になる場合があります。

基本的な使い方(Query Builder)

Query BuilderでwhereNotを使う最もシンプルな例です。

// statusが'active'以外のユーザーを取得
$users = DB::table('users')
    ->whereNot('status', 'active')
    ->get();

生成されるSQL:

SELECT * FROM `users` WHERE NOT (`status` = 'active')

Eloquentモデルでの使い方

Eloquentモデルでも同様に使用できます。

// Eloquentモデルで使う場合
$users = User::whereNot('status', 'active')->get();

// スコープと組み合わせる
$users = User::active()->whereNot('role', 'guest')->get();

生成されるSQL:

SELECT * FROM `users` WHERE NOT (`status` = 'active')
SELECT * FROM `users` WHERE `is_active` = 1 AND NOT (`role` = 'guest')

複数条件での使用

whereNotは他のwhere系メソッドと自由に組み合わせられます。

// roleが'user'かつstatusが'active'以外
$users = DB::table('users')
    ->where('role', 'user')
    ->whereNot('status', 'active')
    ->get();

生成されるSQL:

SELECT * FROM `users`
WHERE `role` = 'user' AND NOT (`status` = 'active')

クロージャを使った複合否定条件

複数カラムをまとめて否定したい場合は、クロージャを渡します。

// statusが'inactive'かつroleが'guest'でないレコードを除外
$users = DB::table('users')
    ->whereNot(function ($query) {
        $query->where('status', 'inactive')
              ->where('role', 'guest');
    })
    ->get();

生成されるSQL:

SELECT * FROM `users`
WHERE NOT (`status` = 'inactive' AND `role` = 'guest')

whereNotInの使い方

複数の値を一括で除外したいときはwhereNotInが適しています。構文の要点だけを素早く確認したい場合はwhereNotIn — 指定キーの値が集合に含まれない要素だけ残すもあわせてご覧ください。

// departmentがHRまたはFinance以外のユーザーを取得
$users = DB::table('users')
    ->whereNotIn('department', ['HR', 'Finance'])
    ->get();

生成されるSQL:

SELECT * FROM `users`
WHERE `department` NOT IN ('HR', 'Finance')

whereNotとwhereNotInの違い: whereNot('col', 'val')は単一値の除外、whereNotIn('col', [...])は複数値の除外に使います。除外したい値が1つでも配列にできるwhereNotInで書くことで、後から値を追加しやすくなります。逆に「配列内の値だけを抽出したい(除外の逆)」場合はwhereIn — 指定キーの値が配列に含まれる要素だけを抽出を使います。

whereNotInにサブクエリを渡す

除外リストが別テーブルの検索結果から決まる場合は、配列の代わりにクロージャでサブクエリを渡せます。

// 退会済みユーザーIDに含まれないユーザーを取得
$users = DB::table('users')
    ->whereNotIn('id', function ($query) {
        $query->select('user_id')
              ->from('withdrawals');
    })
    ->get();

生成されるSQL:

SELECT * FROM `users`
WHERE `id` NOT IN (SELECT `user_id` FROM `withdrawals`)

whereNotInにEloquentコレクション/クエリを渡す

配列の代わりにEloquentのクエリビルダインスタンスを直接渡すこともできます。

$users = User::whereNotIn('id', Order::where('status', 'cancelled')->select('user_id'))->get();

// コレクションのIDだけを配列で渡す場合
$excludedIds = Order::where('status', 'cancelled')->pluck('user_id');
$users = User::whereNotIn('id', $excludedIds)->get();

whereNotInに空配列を渡した場合の注意

whereNotIn('col', [])のように空配列を渡すと、除外対象が0件のため全件が条件を満たす扱いになります(whereIn('col', [])は逆に全件が該当しなくなります)。動的に配列を組み立てる場合、意図せず空配列になっていないか確認してください。

// $excludedIds が空の場合、絞り込みなしで全件取得される
$users = User::whereNotIn('id', $excludedIds)->get();

doesntHave()でリレーションの否定条件を書く

リレーション(has one/has many)が存在しないレコードを取得する場合はdoesntHave()を使います。

// 注文が1件もないユーザーを取得
$users = User::doesntHave('orders')->get();

// 条件付きの否定リレーション
$users = User::whereDoesntHave('orders', function ($query) {
    $query->where('status', 'completed');
})->get();

複数テーブルにまたがるリレーションの条件をさらに掘り下げたい場合は、Eloquentでの複数テーブルのデータ統合:Laravel Joinの実践ガイドも参考になります。

生成されるSQL:

-- doesntHave
SELECT * FROM `users`
WHERE NOT EXISTS (
    SELECT * FROM `orders` WHERE `orders`.`user_id` = `users`.`id`
)

-- whereDoesntHave(条件付き)
SELECT * FROM `users`
WHERE NOT EXISTS (
    SELECT * FROM `orders`
    WHERE `orders`.`user_id` = `users`.`id`
    AND `status` = 'completed'
)

否定条件を書くときの注意点

1. NULLの扱いに注意

whereNot('col', null)は期待通りに動作しません。NULLとの比較には専用メソッドを使います。

// NG: whereNot('deleted_at', null) は正しく動かない場合がある
// OK: whereNotNull を使う
$users = User::whereNotNull('deleted_at')->get();

2. インデックスの利用

NOT条件はインデックスが効きにくい場合があります。大量データに対して使う場合はEXPLAINで実行計画を確認してください。

EXPLAIN SELECT * FROM `users` WHERE NOT (`status` = 'active');

クエリビルダとEloquentのデータベース操作全般を体系的に見直したい方は、Laravel Eloquentで効率的にデータベース操作を行うための完全ガイドもあわせてご覧ください。

3. orWhereNotは意図した括弧になるか確認

OR条件と組み合わせる場合、括弧の優先順位に注意が必要です。

// 意図通りの括弧付けにはwhere()でグループ化する
$users = DB::table('users')
    ->where(function ($query) {
        $query->where('role', 'admin')
              ->orWhereNot('status', 'active');
    })
    ->get();

4. whereNot vs where(‘!=’)の違い

whereNot('col', 'val')NOT (col = 'val')を生成し、where('col', '!=', 'val')col != 'val'を生成します。通常は同じ結果ですが、クロージャを渡した複合条件ではwhereNotのほうが意図を明確に表現できます。

実践例:ユーザー管理システム

管理者ユーザーのリストを出力する際に、ログイン状態が無効であるユーザーを除外する例です。

$adminUsers = User::where('role', 'admin')
    ->whereNot('is_logged_in', false)
    ->whereNotNull('last_login_at')
    ->get();

生成されるSQL:

SELECT * FROM `users`
WHERE `role` = 'admin'
  AND NOT (`is_logged_in` = 0)
  AND `last_login_at` IS NOT NULL

FAQ

Q. whereNotとは?

Laravel 8.57以降で使えるクエリビルダのメソッドです。指定した条件に一致しないレコードをフィルタリングします。whereNot('status', 'active')は「statusが’active’でないレコード」を取得します。内部的にNOT (条件)のSQLを生成します。

Q. whereNotInとの違いは?

whereNot('col', 'val')は単一値の除外(NOT (col = 'val'))、whereNotIn('col', ['a', 'b'])は複数値の除外(col NOT IN ('a', 'b'))です。除外したい値が複数あるときはwhereNotInを使います。

Q. リレーションの否定条件はどう書く?

リレーションが存在しないことを条件にするにはdoesntHave('relation')を使います。条件付きで「特定の状態のリレーションを持たない」場合はwhereDoesntHave('relation', function($q){ ... })を使います。whereNotはカラム値の比較専用で、リレーションの存在確認には使えません。

Q. whereNotInに空配列を渡すとどうなる?

絞り込み条件が無効になり、結果的に全件がヒットします(除外対象が0件のため)。逆にwhereIn('col', [])は全件が非該当になります。動的に配列を組み立てる実装では、意図せず空配列が渡っていないか事前にチェックしておくと安全です。

Q. whereNotInにサブクエリや別モデルのクエリを渡せますか?

渡せます。配列だけでなく、クロージャによるサブクエリやModel::where(...)->select('column')のようなクエリビルダインスタンスも指定できます。除外リストが別テーブルの検索結果に依存する場合は、事前に配列化せずサブクエリのまま渡す方がクエリ回数を減らせます。

ソフトデリートされたレコードを条件に含めたい・除外したい場合は、Laravel withTrashedを活用してソフトデリートされたデータを簡単に管理する方法も参考にしてください。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント