带有 Eloquent 过滤器的复杂查询

Complex query with filters with Eloquent

我有下表(和相关字段):

users           (id, name)
companies       (id, name)
sales           (id, company_id, user_id)
products        (id, description)
products_sales  (id, product_id, sale_id)

现在,我生成了一些带有多个并发过滤器的报告: 公司名,用户名,产品。

我希望公司成为结果中的 'top level' 对象。 所以,

Company::where('name', 'LIKE', "%$company_name%")

然后

  ->whereHas('sales', function ($query) use ($user_name) {
      $query->whereHas('user', function ($query2) use ($user_name) {
          $query2->where('name', 'LIKE', "%$user_name%")
      });
  });

这个已经够复杂了,我也想全部加载 关系,以优化数据库访问,所以我最终得到:

$cond = function($q) use ($user_name) {
    $cond2 = function ($q2) use ($user_name) {
        $q2->where('name', 'LIKE', "%$user_name%");
    };

    $q->whereHas('user', $cond2)
      ->with(['user' => $cond2]);
};
Company::where('name', 'LIKE', "%$company_name%")
    ->whereHas('sales', $cond)
    ->with(['sales' => $cond]);

我觉得这样会带来不必要的重复。此外,过滤时 产品以同样的方式,我认为预先加载取代了以前的。

那么...执行此操作的更好方法是什么? 我可以为此使用 Eloquent 还是应该返回到 'raw' 查询?

您可以使用 getQueryLog 检查 Eloquent 生成的查询。如果您的查询正在查找 "less than optimal",您可以考虑使用原始查询。

如果您的查询是最优的,但 Eloquent 语法看起来太 "messy",您可以考虑将部分 query scopes 构建到您的模型中。

例如,您可以在 Sales 模型中定义一个范围来匹配名称:

class Sales extends Model
{
    /**
     * Scope a query to only include Sales for users with a matching name.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeMatchUserName($query, $user_name)
    {
        $cond2 = function ($q2) use ($user_name) {
            $q2->where('name', 'LIKE', "%$user_name%");
        };

        return $query->with(['user' => $cond2])
                     ->whereHas('user', $cond2);
    }
}

那么你的查询就变得更简单了(看起来):

$cond = function($q) use ($user_name) {
    return $q->matchUserName($user_name);
};

Company::where('name', 'LIKE', "%$company_name%")
    ->with(['sales' => $cond])
    ->whereHas('sales', $cond);

您甚至可以将 那个 查询打包到您的 Company 模型中:

class Company extends Model
{
    /**
     * Scope a query to only include Companies and Sales for a matching user and company name.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeMatchCompanyAndUserName($query, $user_name, $company_name)
    {
        $cond = function($q) use ($user_name) {
            return $q->matchUserName($user_name);
        };

        return $query->where('name', 'LIKE', "%$company_name%")
            ->with(['sales' => $cond])
            ->whereHas('sales', $cond);
    }
}

那么您需要做的就是:

Company::matchCompanyAndUserName($user_name, $company_name);