【问题标题】:Laravel Eloquent "with()" eager loading & "whereHas()" conditionsLaravel Eloquent "with()" 急切加载 & "whereHas()" 条件
【发布时间】:2019-07-08 18:46:30
【问题描述】:

我有 2 个表:'Author' 和 'Book',具有以下简单的关系:

class Author extends Model
{
    public function books() {
        return $this->hasMany('App\Book');
    }
}

class Book extends Model 
{
    public function author() {
        return $this->belongsTo('App\Author');
    }
}

Book 模型有一个属性price(浮点数)和category(字符串)。

我正在尝试找到一种在 1 个查询中完成这 3 件事的有效方法:

  1. category comedycreated_at 在 date1 和 date2 之间获取具有 books 的所有 authors 的列表。
  2. 列表中的作者应该有一个额外的属性 highest_price,这是所有符合步骤 1 条件的作者书籍中的最高价格。
  3. 图书列表应按price 降序排序,作者的最终结果列表也应按该新属性highest_price 降序排序。

结果一定是这样的:

[
    {
        id: 1,
        name: 'Author ABC'
        highest_price: 15
        books: [
            {
                id: 1,
                name: 'book1',
                category: 'comedy',
                price: 15
            },
            {
                id: 2,
                name: 'book2',
                category: 'comedy',
                price: 10
            }
        ]
    },
    {
        id: 10,
        name: 'Author XYZ'
        highest_price: 12
        books: [
            {
                id: 3,
                name: 'book3',
                category: 'comedy',
                price: 12
            }
        ]
    }
]

到目前为止我得到了什么:

Author::with(['books' => function($query) {
    $query->where('category', 'comedy')
        ->where('created_at', '>=', $someFromDate)
        ->where('created_at', '<=', $someToDate);
}])
->has('books', '>', 0)
->get()
->dd();

但这只是给了我所有有书的作者,它没有考虑急切加载中的条件......

甚至可以在 1 个查询中使用吗? 任何帮助将不胜感激!

解决方案

感谢答案,我发现我需要的是 with()whereHas() 的组合(在这里找到答案:https://stackoverflow.com/a/29594039/5297218)。因为“where”条件必须在两种方法中都有,所以我在 Author 模型上使用了 query scope

public function scopeWithAndWhereHas($query, $relation, $constraint){
  return $query->whereHas($relation, $constraint)
               ->with([$relation => $constraint]);
}

对于额外的highest_price 属性,我在Collection 上使用了transform() 方法,并结合max() 方法来获得最高价格。这是最终结果:

Author::withAndWhereHas('books', function($query) use ($category, $date){
    $query->where('category', $category)
        ->where('created_at', '>=', $date->get('start'))
        ->where('created_at', '<=', $date->get('end'))
        ->orderBy('price', 'desc');
})
->get()
->transform(function ($author){
    $author['highest_price'] = $author->books->max('price');
    return $author;
})
->sortByDesc('highest_price')
->values();

ps:这个解决方案的性能是我之前的 42 倍!

【问题讨论】:

    标签: laravel eloquent query-builder laravel-5.7 laravel-collection


    【解决方案1】:

    with() 上的子查询将限制加载到作者与书籍的关系中的内容,但不会限制返回的作者关系。您是否尝试过whereHas() 而不是with()?这只会返回拥有符合该标准的书籍的作者。这可以通过一个查询来实现,但这将取决于您所需要的一切。添加字段highest_price 可能会更好地适应不同的关系。

    Author::with('books', 'highest_price')->whereHas(['books' => function($query) use($someFromDate, $someToDate) {
    $query->where('category', 'comedy')
        ->where('created_at', '>=', $someFromDate)
        ->where('created_at', '<=', $someToDate);
    }])
    ->get()->sortByDesc('highest_price.highest_price');
    

    如果您有任何问题,请告诉我。

    【讨论】:

    • 太棒了! whereHas() 正是我想要的。现在我有了作者列表,但是如何添加包含与whereHas() 标准匹配的他/她的书籍列表中的highest_price 的额外属性?
    • 我喜欢您在@SebSob 发布的解决方案,它可以满足您的需求。我只是要注意transform() 会导致 PHP 遍历集合,如果集合变得太大(数百万以上,所以现在没什么大不了的),这会减慢你的速度。不过,有几种方法可以将变异字段添加到查询返回的内容中,因此如果您在某个时候遇到速度变慢的情况,您可以添加它而无需使用转换。
    • 感谢@N Mahurin - 感谢您的提示!我知道我可以将 mutator getHighestPriceAttribute() 添加到 Author 模型中。但是,它必须匹配给定的条件(类别和日期之间);而且我不确定如何将它们传递给突变器。另外,mutator 会为每条记录查询数据库吗?不确定性能是否更好?
    • 这样的模型上的 mutator 的问题是,在您调用 get() 以返回集合和完整数据集之前,您无法对其进行排序。在它变得如此之大之后,我们希望避免这种情况,因为 PHP 在一定大小的速度方面无法与数据库保持同步。但是当您有足够的记录来证明这一点时,您还将使用分页和其他技巧来缩短页面加载时间。
    • 好的。当然,我只会展示最多 50 位作者,每位作者最多 100 本书。
    【解决方案2】:

    使用关系条件有两种不同的结果:

    1- 根据条件限制 RELATIONSHIP 结果(这是您正在做的,但不是您想要的):

    $authors = Author::with(['books' => function($query) {
        $query->whereNotNull('published');
    }])->get();
    

    在这里,您将拥有所有作者,无论他们是否已出版书籍。那些有出版书籍的人将有书籍信息。所有其他人都不会。

    2- 根据关系条件限制 PARENT 结果(这是您应该使用的):

    $authors = Author::whereHas('books', function($query) {
         $query->whereNotNull('published');
    })->get();
    

    在这里,您将只看到已出版书籍的作者。

    注意:如果你想预先加载你的 Book 模型,你仍然需要使用 ->with('books')

    【讨论】:

    • 感谢您的清晰解释! whereHas() 确实是我想要的!
    • 另外,如果我渴望加载图书模型-&gt;with('books'),将添加所有作者的图书,而不仅仅是那些与whereHas()criteria 匹配的图书。有什么想法吗?
    【解决方案3】:

    根据您问题中的结果集,下面应该可以满足您的需求:

    Author::whereNotNull('highest_price')
        ->has('books')
        ->with(['books' => function($query) use($startDate, $endDate){
            return $query->where('category', 'comedy')
                ->whereBetween('created_at', [$startDate, $endDate])
                ->orderBy('price', 'desc')
                ->select('id', 'name', 'category', 'price', 'author_id');
        }])
        ->orderBy('highest_price', 'desc')
        ->select('id', 'name', 'highest_price')
        ->get()
    

    解释:

    解决这些问题的简单方法是首先构建父查询构建器,在本例中为作者。因此,根据您的要求,我们检查了最高价格的存在,过滤了至少有 1 本书的作者,按最高价格降序排序,并从作者表中选择了我们需要的列。

    现在,查看预加载,我们做了同样的事情,过滤了 created_at 日期范围,按价格降序排序并选择了所需的列。我特意添加了额外的列 author_id 这是外键,以便显示书籍结果。

    【讨论】:

    • 谢谢,但是这样不行,因为作者没有highest_price的专栏,这是必须即时添加的东西,它拥有作者匹配的书籍的最高价格标准
    猜你喜欢
    • 1970-01-01
    • 2021-10-02
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    • 2018-03-29
    • 2013-06-10
    • 2016-08-17
    • 1970-01-01
    相关资源
    最近更新 更多