【问题标题】:Get last from relation where is condition从条件的关系中获取最后一个
【发布时间】:2018-12-01 04:23:48
【问题描述】:

我有两张表是一对多的关系。我的任务很简单,我需要关系表中应用条件的每个父项的最后一项。

第一个表是状态:

| id | partner_id | status | created_at          |
|----|------------|--------|---------------------|
| 1  | 1          | 1      | 2018-01-01 00:00:00 |
| 2  | 1          | 5      | 2018-02-01 00:00:00 |
| 3  | 2          | 1      | 2018-01-01 00:00:00 |

关系是

public function partner()
{
    return $this->belongsTo(Partner::class, 'partner_id');
}

其次是合作伙伴:

| id | name      |
|----|-----------|
| 1  | partner_1 |
| 2  | partner_2 |

关系是:

public function status()
{
    return $this->hasMany(Status::class, 'partner_id')->orderBy('id', 'desc');
}

假设我想让所有合作伙伴的最后状态为 1。我试过这个:

Partner::with('status')
    ->whereHas('status', function ($query) use ($status) {
          $query->where('status', $status);
       })
    ->get();

结果是这样的:

"select * from `partners` where exists (select * from `status` where `partners`.`id` = `status`.`partner_id` and `status` = 1)"

但是有了它,我也会得到 partner_1,即使他的最后状态是 5,但在历史上他的状态是 1。

我创建了 sql 查询来得到这个,但我认为做这样一个简单的任务是相当复杂的。而且我认为必须有一些简单的 Laravel 方法:

select p.id, ps.status, ps.newest_status
from partners as p
INNER JOIN (SELECT a.created_at AS newest_status, a.partner_id, a.status
              FROM status as a
                INNER JOIN (
                  SELECT max(created_at) as newest_status, partner_id
                FROM status
                GROUP BY partner_id
              ) as b ON a.partner_id = b.partner_id AND a.created_at = b.newest_status
) ps ON p.id = ps.partner_id and ps.status = 1
GROUP BY p.id

我的目标是只获得 partner_2,因为只有他的最后状态 1。

【问题讨论】:

    标签: php laravel eloquent one-to-many laravel-5.7


    【解决方案1】:

    一种方法是自定义关系方法以包含一些排序(如@yrv16 所建议)

    但是,如果您想要效率,那么要走的路就是子查询。一个非常 good article by J. Reinink 很好地解释了这一点。

    简而言之,就是这样:

    class Partner extends Model {
    
        // ...
    
        public function scopeWithLastStatus($query)
        {
            $query->addSubSelect('last_status', Status::select('status')->latest());
        }
    
        // ...
    

    这会将“虚拟”属性last_status 添加到您的模型中。

    然后你可以这样做:

    Partner::withLastStatus()->get()->where('last_status', 'idle');
    

    【讨论】:

    • 结果是select `partners`.*, (select `status` from `status` order by `created_at` desc limit 1) as `last_status` from `partners` where `last_status` = 1,这个结果是无效的,因为你不能在哪里调用别名。所以它不起作用。
    • 完整阅读文章。 addSubSelect 方法是需要添加的宏。
    • 或者使用包 reinink/advanced-eloquent
    • 我读过这篇文章,但在文章中没有使用任何变量。
    • 我不知道那个。您可以通过使用基本 Collection 函数在查询后进行过滤来牺牲一些效率:Partner::withLastStatus()->get()->where('last_status', 'idle');
    【解决方案2】:

    你可以用这个:

    Partner::select('partners.*')
        ->join('status', 'partners.id', 'status.partner_id')
        ->where('status.status', $status)
        ->where('status.id', function($query) {
            $query->select('id')
                ->from('status')
                ->whereColumn('partner_id', 'partners.id')
                ->orderByDesc('id')
                ->limit(1);
        })->get();
    

    【讨论】:

      猜你喜欢
      • 2021-12-09
      • 2018-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-17
      • 2022-11-23
      • 1970-01-01
      • 2021-04-21
      相关资源
      最近更新 更多