Reader Stacks

Useful One-Off Eloquent Query Tricks in Laravel

inRandomOrder() genuinely shuffles at the database level for a small table, but it becomes a real performance concern once a table grows into the millions of rows — worth knowing before reaching for it blindly.

A handful of genuinely useful but easy-to-forget Eloquent query techniques — getting random rows, pulling a single column's value directly, concatenating columns in a search, and getting the last record within a grouped query — come up often enough to be worth a shared reference.

Getting random rows

$randomProducts = Product::inRandomOrder()->limit(5)->get();

inRandomOrder() orders by a random value at the database level (ORDER BY RAND() on MySQL) — genuinely fine for a small-to-medium table, but this becomes a real performance concern on a very large table, since the database has to compute and sort a random value across every single row before applying the limit, which is inherently more expensive than a normal indexed order-by.

Getting just one column's value, without loading the full model

$productName = Product::where('id', 5)->value('name');

value() returns a single scalar value directly, rather than a full model instance — more efficient than Product::find(5)->name when only that one specific field is actually needed, since it can generate a query selecting just that one column instead of every column on the table.

Getting one column's value from every matching row, as a flat array

$productNames = Product::where('category_id', 3)->pluck('name');
$productNamesById = Product::pluck('name', 'id'); // ['1' => 'Mouse', '2' => 'Keyboard', ...]

pluck() with a second argument returns an associative collection keyed by that column — genuinely useful for quickly building a dropdown's options list (ID as the key, display name as the value) without a manual loop transforming a full model collection.

Searching across two concatenated columns

$customers = Customer::whereRaw("CONCAT(first_name, ' ', last_name) LIKE ?", ["%{$search}%"])->get();

A raw CONCAT() expression is necessary here since a search term like "John Smith" wouldn't match either first_name or last_name individually — this lets the search match against the two columns combined as a full name, the way a user would naturally expect to search for someone by their complete name.

Getting the last record within each group

$latestOrderPerCustomer = Order::select('orders.*')
    ->join(DB::raw('(SELECT customer_id, MAX(created_at) as max_created_at FROM orders GROUP BY customer_id) as latest'),
        function ($join) {
            $join->on('orders.customer_id', '=', 'latest.customer_id')
                ->on('orders.created_at', '=', 'latest.max_created_at');
        })
    ->get();

This genuinely awkward-looking subquery-join pattern is one standard way to answer "give me the single most recent order per customer" — a plain groupBy() alone can't do this directly, since SQL's GROUP BY only returns one aggregated value per group, not an entire matching row's worth of other columns.

A simpler alternative using a window function, on MySQL 8+

$latestOrders = DB::table('orders')
    ->select('*')
    ->fromSub(function ($query) {
        $query->from('orders')
            ->selectRaw('*, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at DESC) as rn');
    }, 'ranked')
    ->where('rn', 1)
    ->get();

ROW_NUMBER() with PARTITION BY, available on MySQL 8.0+ and other modern databases, expresses "the most recent row per group" more directly than the subquery-join approach above — worth using instead when the database version is confirmed to support window functions.

When these tricks are worth reaching for versus a simpler approach

Each of these solves a genuinely specific, narrow problem that a plain where()/orderBy() chain can't express directly — worth knowing they exist for when the specific need arises, but not worth reaching for by default when a simpler standard query already does the job.