blog-contest-may-mayhem icon indicating copy to clipboard operation
blog-contest-may-mayhem copied to clipboard

Get the latest record on the group in the most Eloquent way 😎

Open akaamitgupta opened this issue 6 years ago • 7 comments

https://medium.com/@akaamitgupta/get-the-latest-record-on-the-group-in-the-most-eloquent-way-50597a588361

akaamitgupta avatar May 23 '18 16:05 akaamitgupta

Good tip!

Remembered I had to solve this in the past too. For those interested:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;

trait GroupedLastScope
{
    /**
     * Get the latest entry for each group.
     *
     * Each group is composed of one or more columns that make a unique combination to return the
     * last entry for.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param array|null $fields A list of fields that's considered as a unique entry by the query.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeLastPerGroup(Builder $query, ?array $fields = null) : Builder
    {
        return $query->whereIn('id', function (QueryBuilder $query) use ($fields) {
            return $query->from(static::getTable())
                ->selectRaw('max(`id`)')
                ->groupBy($fields ?? static::$groupedLastScopeFields);
        });
    }
}

sebastiaanluca avatar May 24 '18 14:05 sebastiaanluca

Good tip!

Remembered I had to solve this in the past too. For those interested:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;

trait GroupedLastScope
{
    /**
     * Get the latest entry for each group.
     *
     * Each group is composed of one or more columns that make a unique combination to return the
     * last entry for.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param array|null $fields A list of fields that's considered as a unique entry by the query.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeLastPerGroup(Builder $query, ?array $fields = null) : Builder
    {
        return $query->whereIn('id', function (QueryBuilder $query) use ($fields) {
            return $query->from(static::getTable())
                ->selectRaw('max(`id`)')
                ->groupBy($fields ?? static::$groupedLastScopeFields);
        });
    }
}

Usage example?

exSnake avatar Jun 23 '19 19:06 exSnake

@sebastiaanluca has some exemple?

joefreire avatar Feb 06 '20 19:02 joefreire

My memory's a little foggy trying to remember this use case 😅 Given the example in the opening post where a users has a lot of messages and we want the last message per user:

$latest = Message::query()
    ->lastPerGroup(['user_id'])
    ->get()

Which will (should) return a list of recent messages, one per user.

If you want to be sure you're getting the latest by date instead of by auto-incremented ID, I suppose you can change max(id) to max(created_at) in the scope.

sebastiaanluca avatar Feb 07 '20 14:02 sebastiaanluca

Good tip!

Remembered I had to solve this in the past too. For those interested:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;

trait GroupedLastScope
{
    /**
     * Get the latest entry for each group.
     *
     * Each group is composed of one or more columns that make a unique combination to return the
     * last entry for.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param array|null $fields A list of fields that's considered as a unique entry by the query.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeLastPerGroup(Builder $query, ?array $fields = null) : Builder
    {
        return $query->whereIn('id', function (QueryBuilder $query) use ($fields) {
            return $query->from(static::getTable())
                ->selectRaw('max(`id`)')
                ->groupBy($fields ?? static::$groupedLastScopeFields);
        });
    }
}

Where should I put this code?

I have tried to make this work and I have not been able to achieve it.

eduardr10 avatar Feb 09 '20 18:02 eduardr10

Good tip! Remembered I had to solve this in the past too. For those interested:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;

trait GroupedLastScope
{
    /**
     * Get the latest entry for each group.
     *
     * Each group is composed of one or more columns that make a unique combination to return the
     * last entry for.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param array|null $fields A list of fields that's considered as a unique entry by the query.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeLastPerGroup(Builder $query, ?array $fields = null) : Builder
    {
        return $query->whereIn('id', function (QueryBuilder $query) use ($fields) {
            return $query->from(static::getTable())
                ->selectRaw('max(`id`)')
                ->groupBy($fields ?? static::$groupedLastScopeFields);
        });
    }
}

Where should I put this code?

I have tried to make this work and I have not been able to achieve it.

It's a trait, in a different php file then "Use GroupedLastScope" on the top of your model

exSnake avatar Feb 10 '20 14:02 exSnake

What if the table doesn't have a id column?

sinall avatar Apr 29 '20 04:04 sinall