Laravel获取每个分组的最新记录

4

我正在尝试将一些原始SQL迁移到我的模型上的Eloquent(或Query Builder)范围。我的零件历史表看起来像这样:

+----+---------+--------+------------+
| id | part_id | status | created_at |
+----+---------+--------+------------+
|  1 |       1 |      1 | ...        |
|  2 |       1 |      2 | ...        |
|  3 |       2 |      1 | ...        |
|  4 |       1 |      2 | ...        |
|  5 |       2 |      2 | ...        |
|  6 |       1 |      3 | ...        |

注意,同一part_id可能会有多个状态相同的条目。

目前我使用以下方法选择最新的状态:

$part = Part::leftjoin( DB::raw("
 (SELECT t1.part_id, ph.status, t1.part_status_at 
  FROM (
    SELECT part_id, max(created_at) part_status_at
    FROM part_histories
    GROUP BY part_id) t1 
  JOIN part_histories ph ON ph.part_id = t1.part_id AND t1.part_status_at = ph.created_at) as t2
  )", 't2.part_id', '=', 'parts.id')->where( ... )

我正在尝试从这个模型中制作零件模型,到目前为止我有以下内容:

public function scopeWithLatestStatus($query)
{
    return $query->join(DB::raw('part_histories ph'), function ($join) {
         $join->on('ph.part_id', '=', 't1.id')->on('t1.part_status_at', '=', 'ph.created_at');
      })
      ->from(DB::raw('(select part_id as id, max(created_at) part_status_at from part_histories GROUP BY part_id) t1'))
      ->select('t1.id', 'ph.part_status', 't1.part_status_at');
}

我已经做了一部分(但仍在使用一些原始的SQL),但我无法弄清剩下的部分。


1
只是出于好奇,为什么?如果原始的SQL可以正常工作,那么从切换到ORM中你会得到什么好处呢?我知道这与问题实际上无关,我只是好奇。 - GordonM
1
好观点,戈登 :-) 主要是因为我正在将其他东西更改为ORM。事实证明,我已经决定坚持使用原始的SQL,因为我没有真正解释清楚我的问题。 - rareclass
1个回答

4
你可以将查询重写为左连接以获得相同的结果。
select a.* 
from part_histories a
left join part_histories b on a.part_id = b.part_id 
                            and a.created_at < b.created_at
where b.part_id is null

我猜你可以轻松地将上述查询在你的作用域中进行转换,类似于:
public function scopeWithLatestStatus($query)
{
    return $query->leftJoin('part_histories as b', function ($join) {
                $join->on('a.part_id', '=', 'b.part_id')
                     ->where('a.created_at', '<', 'b.created_at');
            })
        ->whereNull('b.part_id')
        ->from('part_histories as a')
        ->select('a.*');
}

Laravel Eloquent选择最大created_at的所有行

Laravel - 获取每个UID类型的最新条目

Laravel Eloquent按最近记录分组

编辑上面的查询,使用has关系,获取每个零件的最新历史记录,您可以定义一个像下面这样的hasOne关系:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class Part extends Model
{
    public function latest_history()
    {
        return $this->hasOne(\App\Models\PartHistory::class, 'part_id')
            ->leftJoin('part_histories as p1', function ($join) {
                $join->on('part_histories.part_id', '=', 'p1.part_id')
                    ->whereRaw(DB::raw('part_histories.created_at < p1.created_at'));
            })->whereNull('p1.part_id')
            ->select('part_histories.*');
    }
}

然后,要加载带有其最新历史记录的部分,您可以将上述定义的映射作为贪婪加载。

$parts = Part::with('latest_history')->get();

您将获得一个零件列表以及最新的历史记录,如下所示:
Array
(
    [0] => Array
        (
            [id] => 1
            [title] => P1
            [latest_history] => Array
                (
                    [id] => 6
                    [created_at] => 2018-06-16 08:25:10
                    [status] =>  1
                    [part_id] => 1
                )

        )
....
)

谢谢,但是我的错误在于我没有将我的问题表述清晰 - 你所提供的方法可以很好地解决问题 - 但是我已经使用不同的方法解决了这个问题。 我应该问如何更改作用域方法来将最新状态附加到任何ORM部分查询中,如:Part::withLatestStatus()->get()。 - rareclass
@rareclass,你想从你的Part模型中加载这些数据吗?而不是应用作用域。 - M Khalid Junaid
你的意思是要把目前的“hasMany”关系改变吗? - rareclass
@rareclass 我已经更新了我的答案, 在part模型上定义一个新的hasOne来加载所需的相关数据。 - M Khalid Junaid
1
有意思,我甚至不知道你可以用一对一关系这样做。 - rareclass
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接