Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

stefaan's avatar

Elegant eloquent solution

Hi,

I'm searching the most elegant eloquent way for the following DB query: There are two models/tables: items (id, timestamp, ...) transactions (id, timestamp)

Every week some items are created, once or twice a week a transaction is done. I need to get a list of all items created between two transactions, e.g.:

transaction 8 (2022-05-30 to 2022-06-02)

  • item 10 (2022-06-01)
  • item 9 (2022-05-31)

transaction 7 (2022-05-25 to 2022-05-39)

  • item 8 (2022-05-29)
  • item 7 (2022-05-26) ...

I thought about two ways: Get a single row for every transaction period and query all items between the two timestamps like: https://stackoverflow.com/questions/20849098/mysql-how-get-value-in-previous-row-and-value-in-next-row

Fetch all transaction rows except the oldest. Query all items between two rows transaction timestamp (i) and (i+1)

Do you have any ideas for an elegant solution?

Thank you! Stefan

0 likes
2 replies
xoca's avatar
xoca
Best Answer
Level 6

It boils down to fetching items within 2 dates, so I would create a scope for the Item model that filters by created_at between a starting and an ending date.

You could then add some other methods to simplify its usage, like, if you want to get items that came between a given transaction and the next, it would be useful to get it with something like:

$items = Items::duringTransacion($transaction);
// or
$items = $transaction->itemsUntilNext();

Both of these would use a similar implementation. Here is an example of the latter:

class Transaction extends Model
{
    // ...

    public function itemsUntilNext()
    {
        // Fetch relevant dates
        $startDate = $this->created_at;
        $endDate = static::where('created_at', '>', $startDate)
            ->orderBy('created_at')
            ->first()
            ->pluck('created_at');
        
        // Get items using Items::scopeCreatedWithin($start, $end)
        return Item::createdWithin($startDate, $endDate)
            ->get();
    }
}
stefaan's avatar

Thank you for your help, works great! The idea of returning a collection of other models is new to me - very elegant.

Just use slice() to rip off the first enty when retrieving the list:

$transactions = Transaction::orderByDesc('created_at')
        ->get()
        ->slice(0, -1);

Todo: create a scope...

class Transaction extends Model
{
// ...
    public function previousItemDate()
    {
        $endDate = $this->created_at;
        $startDate = static::where('created_at', '<', $endDate)
            ->orderByDesc('created_at')
            ->first()
            ->created_at;

        return $startDate;
    }

    public function previousItems()
    {
        $endDate = $this->created_at;
        $startDate = $this->previousItemDate();

        return Item::whereBetween('itemdate', [$startDate, $endDate])
            ->get();
    }

}

Please or to participate in this conversation.