Navigation

Laravel

How to use `whereIn()` with an array of IDs

Filter Laravel queries with multiple values in 2025 using whereIn() and whereNotIn(). Perfect for bulk operations, filtering by categories, and complex WHERE conditions.

Table Of Contents

Laravel's Built-in Solution: Multiple Value Filtering

Instead of multiple OR conditions, whereIn() checks if a column value exists in an array. Much cleaner than chaining OR statements:

// VERBOSE - Multiple OR conditions
$products = Product::where('category_id', 1)
    ->orWhere('category_id', 3)
    ->orWhere('category_id', 5)
    ->orWhere('category_id', 8)
    ->get();

// CLEAN - Single whereIn() statement
$categoryIds = [1, 3, 5, 8];
$products = Product::whereIn('category_id', $categoryIds)->get();

// Real-world example: Get posts by specific authors
$authorIds = [10, 25, 42, 67];
$posts = Post::whereIn('user_id', $authorIds)
    ->where('published', true)
    ->orderBy('created_at', 'desc')
    ->get();

// Exclude specific values with whereNotIn()
$bannedUserIds = [5, 12, 33];
$comments = Comment::whereNotIn('user_id', $bannedUserIds)
    ->where('approved', true)
    ->get();

Advanced whereIn() Query Patterns

Handle complex filtering scenarios with arrays and collections:

// E-commerce: Products from selected brands
$selectedBrands = ['Nike', 'Adidas', 'Puma'];
$brandProducts = Product::whereIn('brand', $selectedBrands)
    ->where('active', true)
    ->where('stock', '>', 0)
    ->get();

// User permission filtering
$allowedRoles = ['admin', 'moderator', 'editor'];
$users = User::whereIn('role', $allowedRoles)
    ->where('email_verified_at', '!=', null)
    ->get();

// Dynamic filtering based on request
$request = request();
$query = Product::query();

if ($request->filled('categories')) {
    $categoryIds = explode(',', $request->categories);
    $query->whereIn('category_id', $categoryIds);
}

if ($request->filled('brands')) {
    $brands = explode(',', $request->brands);
    $query->whereIn('brand', $brands);
}

$filteredProducts = $query->get();

// Status-based filtering
$activeStatuses = ['published', 'featured', 'promoted'];
$activePosts = Post::whereIn('status', $activeStatuses)
    ->where('publish_date', '<=', now())
    ->get();

// Numeric range alternatives
$priceRanges = [100, 200, 500, 1000]; // IDs representing price ranges
$products = Product::whereIn('price_range_id', $priceRanges)->get();

// Better approach for actual price ranges
$products = Product::whereBetween('price', [100, 1000])->get();

Complex whereIn() Scenarios with Relationships

Combine whereIn() with relationship queries and subqueries:

// Users who have posts in specific categories
$techCategoryIds = [1, 5, 12, 18]; // Technology-related categories
$techAuthors = User::whereHas('posts', function ($query) use ($techCategoryIds) {
    $query->whereIn('category_id', $techCategoryIds);
})->get();

// Products with reviews from verified buyers
$verifiedBuyerIds = User::where('verified_buyer', true)->pluck('id');
$trustedProducts = Product::whereHas('reviews', function ($query) use ($verifiedBuyerIds) {
    $query->whereIn('user_id', $verifiedBuyerIds);
})->get();

// Subquery approach - Orders from high-value customers
$vipCustomerIds = User::selectRaw('id')
    ->whereHas('orders', function ($query) {
        $query->selectRaw('SUM(total)')
              ->groupBy('user_id')
              ->havingRaw('SUM(total) > 5000');
    })
    ->pluck('id');

$vipOrders = Order::whereIn('user_id', $vipCustomerIds)->get();

// Alternative using join
$vipOrders = Order::join('users', 'orders.user_id', '=', 'users.id')
    ->whereIn('users.id', function ($query) {
        $query->select('user_id')
              ->from('orders')
              ->groupBy('user_id')
              ->havingRaw('SUM(total) > 5000');
    })->get();

// Performance optimization: Use pluck() for ID arrays
$popularCategoryIds = Category::where('post_count', '>', 100)->pluck('id');
$popularPosts = Post::whereIn('category_id', $popularCategoryIds)->get();

// Relationship-based ID collection
$adminUsers = User::where('role', 'admin')->get();
$adminPostIds = $adminUsers->pluck('posts.*.id')->flatten();
$adminPosts = Post::whereIn('id', $adminPostIds)->get();

// More efficient approach
$adminPosts = Post::whereHas('user', function ($query) {
    $query->where('role', 'admin');
})->get();

Advanced Use Cases and Performance Optimization

Handle large datasets and complex business logic:

// Bulk operations with whereIn()
class ProductService
{
    public function updatePricesForCategories(array $categoryIds, float $multiplier)
    {
        return Product::whereIn('category_id', $categoryIds)
            ->update(['price' => DB::raw("price * {$multiplier}")]);
    }
    
    public function deactivateProductsByBrands(array $brands)
    {
        return Product::whereIn('brand', $brands)
            ->update(['active' => false, 'deactivated_at' => now()]);
    }
    
    public function getRevenueByCategories(array $categoryIds, $startDate, $endDate)
    {
        return OrderItem::join('products', 'order_items.product_id', '=', 'products.id')
            ->join('orders', 'order_items.order_id', '=', 'orders.id')
            ->whereIn('products.category_id', $categoryIds)
            ->whereBetween('orders.created_at', [$startDate, $endDate])
            ->select('products.category_id', DB::raw('SUM(order_items.price * order_items.quantity) as revenue'))
            ->groupBy('products.category_id')
            ->get();
    }
}

// Geographic filtering
$allowedStates = ['CA', 'NY', 'TX', 'FL'];
$regionalOrders = Order::whereHas('shippingAddress', function ($query) use ($allowedStates) {
    $query->whereIn('state', $allowedStates);
})->get();

// Time-based filtering with arrays
$businessHours = [9, 10, 11, 12, 13, 14, 15, 16, 17]; // 9 AM to 5 PM
$businessHourOrders = Order::whereIn(DB::raw('HOUR(created_at)'), $businessHours)->get();

// Multi-dimensional filtering
$filterCriteria = [
    'categories' => [1, 3, 5],
    'brands' => ['Apple', 'Samsung', 'Google'],
    'price_ranges' => ['100-500', '500-1000']
];

$query = Product::query();

if (!empty($filterCriteria['categories'])) {
    $query->whereIn('category_id', $filterCriteria['categories']);
}

if (!empty($filterCriteria['brands'])) {
    $query->whereIn('brand', $filterCriteria['brands']);
}

if (!empty($filterCriteria['price_ranges'])) {
    $query->where(function ($q) use ($filterCriteria) {
        foreach ($filterCriteria['price_ranges'] as $range) {
            [$min, $max] = explode('-', $range);
            $q->orWhereBetween('price', [$min, $max]);
        }
    });
}

$filteredProducts = $query->get();

// Performance considerations for large arrays
class LargeDatasetService
{
    public function processLargeIdArray(array $ids)
    {
        // Chunk large arrays to avoid query length limits
        $chunks = array_chunk($ids, 1000);
        $results = collect();
        
        foreach ($chunks as $chunk) {
            $chunkResults = Product::whereIn('id', $chunk)->get();
            $results = $results->merge($chunkResults);
        }
        
        return $results;
    }
    
    public function efficientBulkUpdate(array $productIds, array $updates)
    {
        // For MySQL, use CASE statements for different updates per ID
        $cases = [];
        $ids = [];
        
        foreach ($updates as $id => $price) {
            $cases[] = "WHEN {$id} THEN {$price}";
            $ids[] = $id;
        }
        
        $caseSql = implode(' ', $cases);
        
        return Product::whereIn('id', $ids)
            ->update(['price' => DB::raw("CASE id {$caseSql} END")]);
    }
}

// API endpoint example
class ProductController extends Controller
{
    public function filterProducts(Request $request)
    {
        $query = Product::query();
        
        // Category filtering
        if ($request->filled('category_ids')) {
            $categoryIds = is_array($request->category_ids) 
                ? $request->category_ids 
                : explode(',', $request->category_ids);
            $query->whereIn('category_id', $categoryIds);
        }
        
        // Brand filtering
        if ($request->filled('brands')) {
            $brands = is_array($request->brands) 
                ? $request->brands 
                : explode(',', $request->brands);
            $query->whereIn('brand', $brands);
        }
        
        // Status filtering
        if ($request->filled('statuses')) {
            $statuses = is_array($request->statuses) 
                ? $request->statuses 
                : explode(',', $request->statuses);
            $query->whereIn('status', $statuses);
        }
        
        return $query->paginate(20);
    }
}

Use whereIn() when filtering by multiple values to replace multiple OR conditions. It's more readable and often more performant than chaining OR statements. For very large arrays (>1000 items), consider chunking to avoid query length limits, and always validate array inputs in API endpoints.

Related: Laravel Collections: Beyond Basic Array Operations | Laravel Events and Listeners: Building Decoupled Applications | Building Multi-tenant Applications with Laravel: A Comprehensive Guide

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel