【问题标题】:Laravel eager loading with limitLaravel 急切加载限制
【发布时间】:2016-02-09 23:29:17
【问题描述】:

我有两个表,分别是“users”和“users_actions”,其中“users_actions”与用户有 hasMany 关系:

用户

id | name | surname | email...

行动

id | id_action | id_user | log | created_at

模型用户.php

class Users {
    public function action()
    { 
       return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc');
    }
}

现在,我想检索所有用户列表以及他们的最后一次操作

我看到这样做Users::with('action')->get(); 只需获取关系的第一个结果,就可以轻松地给我最后的操作:

foreach ($users as $user) {
   echo $user->action[0]->description;
}

但我当然想避免这种情况,只为每个用户选择最后一个操作。

我尝试使用约束,例如

Users::with(['action' => function ($query) {
    $query->orderBy('created_at', 'desc')
          ->limit(1);
    }])
->get();

但这给了我一个不正确的结果,因为 Laravel 执行了这个查询:

SELECT * FROM users_actions WHERE user_id IN (1,2,3,4,5)
ORDER BY created_at
LIMIT 1

这当然是错误的。有没有可能在不使用 Eloquent 对每条记录执行查询的情况下得到这个? 我是否犯了一些我没有看到的明显错误?我对使用 Eloquent 还是很陌生,有时人际关系会困扰我。

编辑:

出于代表性目的,我还需要此功能来在关系中搜索,例如我想搜索 LAST ACTION = 'something' 的用户

我尝试过使用

$actions->whereHas('action', function($query) {
    $query->where('id_action', 1);
});

但这给了我所有有一个动作 = 1 的用户,因为它是一个日志,所以每个人都通过了那一步。


编辑 2:

感谢@berkayk,看来我解决了问题的第一部分,但我仍然无法在关系中搜索。

Actions::whereHas('latestAction', function($query) {
    $query->where('id_action', 1);
});

仍然没有执行正确的查询,它会生成如下内容:

select * from `users` where 
 (select count(*) 
   from `users_action` 
   where `users_action`.`user_id` = `users`.`id` 
   and `id_action` in ('1')
  ) >= 1 
order by `created_at` desc

我需要获取 latest 动作为 1 的记录

【问题讨论】:

标签: php laravel eloquent greatest-n-per-group eager-loading


【解决方案1】:

我为此创建了一个包:https://github.com/staudenmeir/eloquent-eager-limit

在父模型和相关模型中使用 HasEagerLimit 特征。

class User extends Model {
    use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}

class Action extends Model {
    use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}

然后只需在您的急切加载查询中链接->limit(1) 调用(您似乎已经这样做了),您将获得每个用户的最新操作。

【讨论】:

  • 这太完美了,谢谢!令人惊讶的是,这不是开箱即用的功能。
  • d= (◕‿↼ ) 这应该是公认的答案。
【解决方案2】:

要为每个用户加载最新的相关数据,您可以在操作表上使用自联接方法获取它,例如

select u.*, a.*
from users u
join actions a on u.id = a.user_id
left join actions a1 on a.user_id = a1.user_id
and a.created_at < a1.created_at
where a1.user_id is null
a.id_action = 1 // id_action filter on related latest record

要通过查询构建器的方式来完成,您可以将其编写为

DB::table('users as u')
  ->select('u.*', 'a.*')
  ->join('actions as a', 'u.id', '=', 'a.user_id')
  ->leftJoin('actions as a1', function ($join) {
        $join->on('a.user_id', '=', 'a1.user_id')
             ->whereRaw(DB::raw('a.created_at < a1.created_at'));
   })
  ->whereNull('a1.user_id')
  ->where('aid_action', 1) // id_action filter on related latest record
  ->get();

要渴望用户的最新关系,您可以将其定义为模型上的 hasOne 关系,例如

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class User extends Model
{
    public function latest_action()
    {
        return $this->hasOne(\App\Models\Action::class, 'user_id')
            ->leftJoin('actions as a1', function ($join) {
                $join->on('actions.user_id', '=', 'a1.user_id')
                    ->whereRaw(DB::raw('actions.created_at < a1.created_at'));
            })->whereNull('a1.user_id')
            ->select('actions.*');
    }
}

不需要依赖子查询,只需在 whereHas 中应用常规过滤器

User::with('latest_action')
    ->whereHas('latest_action', function ($query) { 
      $query->where('id_action', 1);
    })
    ->get();

【讨论】:

    【解决方案3】:

    Laravel 使用 take() 函数而不是限制

    试试下面的代码我希望它对你有用

    Users::with(['action' => function ($query) {
          $query->orderBy('created_at', 'desc')->take(1);
    }])->get(); 
    

    或者简单地为你的关系添加一个 take 方法,如下所示

     return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc')->take(1);
    

    【讨论】:

    • 同理,take()只是另一个语法糖,生成的查询不会改变
    • 根据 laravel 文档限制或 take 在急切负载上不起作用
    【解决方案4】:

    如果您想轻松获得最新的hasMany 相关模型,我通过@berbayk 链接的解决方案很酷。

    但是,它无法解决您所要求的其他部分,因为使用 where 子句查询此关系将导致几乎与您已经经历的相同 - 所有行都将返回,只有 @ 987654323@ 实际上不是最新的(但最新的匹配 where 约束)。

    所以你去:

    简单的方法 - 获取所有并过滤集合

    User::has('actions')->with('latestAction')->get()->filter(function ($user) {
       return $user->latestAction->id_action == 1;
    });
    

    或困难的方式 - 在 sql 中执行(假设 MySQL):

    User::whereHas('actions', function ($q) { 
    
      // where id = (..subquery..)
      $q->where('id', function ($q) { 
    
        $q->from('actions as sub')
          ->selectRaw('max(id)')
          ->whereRaw('actions.user_id = sub.user_id');
    
      })->where('id_action', 1);
    
    })->with('latestAction')->get();
    

    通过比较性能选择其中一个解决方案 - 第一个将返回所有行并过滤可能的大集合

    后者将运行 子查询 (whereHas) 和嵌套子查询 (where('id', function () {..}),因此这两种方式在大表上可能会很慢。

    【讨论】:

    • 太棒了!我最终使用了您的第二个建议,因为我正在对结果进行分页,而第一个 - 除了可能太大 - 分页时将无法正常工作。我稍微调整了查询​​以匹配我当前的实现(必须使用 IN() 子句),但你让我走上了正确的道路。非常感谢!
    • 要再等 2 个小时才能获得赏金,brb
    【解决方案5】:

    让@berkayk 的代码稍微修改一下。

    Users模型中定义这个关系,

    public function latestAction()
    {
        return $this->hasOne('Action')->latest();
    }
    

    Users::with(['latestAction' => function ($query) {
        $query->where('id_action', 1);
    }])->get();
    

    【讨论】:

    • 但是这硬编码了关系内部的值,这是不可行的
    • 对不起,我不明白该值不应被硬编码。我改变了答案。
    【解决方案6】:

    我认为您所要求的解决方案已在 http://softonsofa.com/tweaking-eloquent-relations-how-to-get-latest-related-model/

    中进行了解释

    在用户模型中定义这个关系,

    public function latestAction()
    {
      return $this->hasOne('Action')->latest();
    }
    

    得到结果

    User::with('latestAction')->get();
    

    【讨论】:

    • 太棒了! +1 只解决了我的部分问题,我如何在这个关系中搜索?说,现在我有最新的,我如何验证最新的记录条件? (请参阅我的问题中的“编辑”部分)
    • 试试这个Users::with(['latestAction' =&gt; function ($query) { $query-&gt;where('id_action', 1); }])
    猜你喜欢
    • 2019-12-20
    • 2017-05-30
    • 2019-05-19
    • 1970-01-01
    • 2014-08-13
    • 2021-04-23
    • 2018-10-15
    • 1970-01-01
    相关资源
    最近更新 更多