Search by

swadhin-sikder / laravel-row-in

swadhin

Adds row value IN and NOT IN support to Laravel's query builder.

Package info

github.com/swadhin-sikder/laravel-row-in

pkg:composer/swadhin-sikder/laravel-row-in

Fund package maintenance!

swadhin-sikder

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-09-19 17:18 UTC

This package is auto-updated.

Last update: 2026-09-19 17:27:23 UTC


README

Laravel Row In

Packagist PHP from Packagist Laravel versions GitHub Workflow Status (master) Total Downloads

Adds row value IN and NOT IN support to Laravel's query builder — the composite-key equivalent of whereIn, so you can match multiple columns against a list of tuples in a single clause instead of chaining orWhere calls.

// Instead of this...
$query->where(function ($query) {
    $query->where('user_id', 1)->where('role_id', 2);
})->orWhere(function ($query) {
    $query->where('user_id', 3)->where('role_id', 4);
});

// ...write this:
$query->whereRowIn(['user_id', 'role_id'], [
    [1, 2],
    [3, 4],
]);

Installation

You can install the package via Composer:

composer require swadhin-sikder/laravel-row-in

The package registers its service provider automatically via Laravel's package discovery — no configuration, config files, or migrations to publish. Once installed, whereRowIn, whereNotRowIn, orWhereRowIn, and orWhereNotRowIn are available on every query builder instance.

PhpStorm support is included through an IDE helper file, so these methods are available to autocomplete and navigation after Composer has indexed the package.

Usage

Basic usage

whereRowIn and whereNotRowIn take an array of columns and an array of value tuples, one tuple per row:

use Illuminate\Support\Facades\DB;

DB::table('user_roles')
    ->whereRowIn(['user_id', 'role_id'], [
        [1, 2],
        [3, 4],
    ])
    ->get();
select * from "user_roles" where ("user_id", "role_id") in ((1, 2), (3, 4))

whereNotRowIn compiles the negated form:

DB::table('user_roles')
    ->whereNotRowIn(['user_id', 'role_id'], [
        [1, 2],
        [3, 4],
    ])
    ->get();
select * from "user_roles" where ("user_id", "role_id") not in ((1, 2), (3, 4))

Any number of columns is supported — two is the common case (composite foreign keys, tenant-scoped IDs), but there's no upper limit:

DB::table('inventory')->whereRowIn(['warehouse_id', 'sku', 'batch'], [
    [1, 'ABC-123', 'B01'],
    [2, 'ABC-123', 'B02'],
]);

Combining with other clauses

Both methods behave like any other where* call and chain normally:

DB::table('users')
    ->where('active', true)
    ->whereRowIn(['user_id', 'role_id'], [[1, 2], [3, 4]])
    ->orderBy('id')
    ->get();

or variants

Use orWhereRowIn / orWhereNotRowIn to combine with the preceding clause using or instead of and:

DB::table('users')
    ->where('is_admin', true)
    ->orWhereRowIn(['user_id', 'role_id'], [[1, 2], [3, 4]])
    ->get();

Subqueries

Pass a Closure, a Builder instance, or anything else Laravel's query builder considers "queryable" instead of an array of values to match against a subquery:

DB::table('users')->whereRowIn(
    ['user_id', 'role_id'],
    DB::table('user_roles')->select(['user_id', 'role_id']),
);
select * from "users" where ("user_id", "role_id") in (
    select "user_id", "role_id" from "user_roles"
)

Collections

$values, and any individual row within it, may be an Illuminate\Support\Collection (or anything implementing Arrayable) instead of a plain array:

DB::table('users')->whereRowIn(
    ['user_id', 'role_id'],
    collect([[1, 2], [3, 4]]),
);

Raw expressions

Individual values within a row can be raw expressions via DB::raw() or Illuminate\Database\Query\Expression:

use Illuminate\Support\Facades\DB;

DB::table('users')->whereRowIn(['user_id', 'role_id'], [
    [DB::raw('current_user_id()'), 2],
]);

Database support

whereRowIn / whereNotRowIn compile to native row-value IN syntax on MySQL, PostgreSQL, and SQLite:

where ("user_id", "role_id") in ((1, 2), (3, 4))

SQL Server has no native row-value IN syntax, so on that grammar the same call is automatically compiled to an equivalent OR-of-AND expression instead:

where (([user_id] = 1 and [role_id] = 2) or ([user_id] = 3 and [role_id] = 4))

You don't need to do anything differently — the correct SQL is chosen automatically based on the connection's grammar.

Subqueries are rejected for row-in clauses on SQL Server because SQL Server does not support the row-value IN syntax required for this form. An InvalidArgumentException is thrown; use an array of rows with SQL Server, or use a database engine with native row-value IN support for subqueries.

Edge cases

  • Empty value lists. whereRowIn($columns, []) compiles to 0 = 1 (never matches) and whereNotRowIn($columns, []) compiles to 1 = 1 (always matches) — the same convention Laravel's own whereIn/whereNotIn use for an empty array, rather than producing invalid SQL.
  • Row/column count mismatches. Every row must have exactly as many values as there are columns. A row with too many or too few values throws an InvalidArgumentException naming the offending row index, before any query bindings are added.
  • Associative rows. Rows may be associative arrays (e.g. ['user_id' => 1, 'role_id' => 2]) — they're normalized to positional order internally, so the column order you pass is always what's matched against.
  • Non-array rows. Passing a flat list instead of a list of tuples (e.g. [1, 2] instead of [[1, 2]]) throws a clear InvalidArgumentException rather than an error deep inside the SQL grammar.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Thank you for considering contributing to Laravel Row In! Please review our contributing guide to get started.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

Laravel Row In is open-sourced software licensed under the MIT license.