Laravel Eloquent在第二桌中获得最大值?

时间:2017-12-01 17:21:37

标签: php laravel eloquent laravel-5.5 laravel-eloquent

我有两张表:listings,其中包含产品的详细信息; bids,其中包含网站的出价历史记录。

要获得简要相关性,请假设listings包含以下字段:名称 category_id 标语 short_description seller_notes

bids我们有两个相关字段: listing_id bid_amount

由于其他地方相关的原因,这需要在Eloquent中,因为我需要访问该模型。

问题似乎与MAX出价金额线或bid_amount的评估有关 - 无论我如何接近它,我总是得到一个不相关的结果,但没有明显的错误我能看到。

$listings = \App\Listing::join('bids', 'listings.id', '=', 'bids.listing_id')->select('listings.*','MAX(bids.bid_amount)');

if( !empty($request->keyword) ) {
    $listings = $listings->where('listings.name','LIKE','%'.$request->keyword.'%')
                ->orWhere('listings.tagline','LIKE','%'.$request->keyword.'%')
                ->orWhere('listings.short_description','LIKE','%'.$request->keyword.'%')
                ->orWhere('listings.seller_notes','LIKE','%'.$request->keyword.'%');
}

if( !empty($request->category) ) {
    $listings = $listings->where('listings.category_id','=',$request->category);
}

if( !empty($request->minimum_bid) && !empty($request->maximum_bid) ) {
    $listings = $listings->whereBetween('bid_amount', [$request->minimum_bid, $request->maximum_bid]);
} else {
    if( !empty($request->minimum_bid) ) {
        $listings = $listings->where('bid_amount', '>', $request->minimum_bid);
    }

    if( !empty($request->maximum_bid) ) {
        $listings = $listings->where('bid_amount', '<', $request->maximum_bid);
    }
}

搜索是查找当前最高出价在minimum_bidmaximum_bid之间的结果(搜索框中的字段)。出现的问题是,可能有多个列表(listing_id)在这些金额之间具有当前最高出价(MAX(bid_amount))。我想显示MAX(bid_amount) bid.listing_id位于minimum_bidmaximum_bid之间的商家信息。这可能会导致多个列表。

等效的MySQL查询应该是:

SELECT * 
FROM   listings 
       JOIN (SELECT listing_id, 
                    Max(bid_amount) AS bid_amount 
             FROM   bids 
             GROUP  BY listing_id) bids 
         ON bids.listing_id = listings.id 
WHERE  ( listings.NAME LIKE '%keyword%' 
          OR listings.tagline LIKE '%keyword%' 
          OR listings.short_description LIKE' %keyword%' 
          OR listings.seller_notes LIKE '%keyword%' ) 
       AND ( bids.bid_amount > minimum_bid ) 
       AND ( bids.bid_amount < maximum_bid ) 

我知道这是愚蠢的,我做错了,我真的可以使用一双新鲜的眼睛。感谢您提供的任何帮助。

4 个答案:

答案 0 :(得分:2)

问题中代码的问题在于,当我们实际需要每个列表的每组出价的最大值时,它会创建一个查询,以尝试查找出价所有的最高出价金额。让我们来看看如何用Eloquent做到这一点。我们将使用local query scopes来封装逻辑并清理API,以便我们可以像这样调用它:

$listings = Listing::withMaximumBidAmount()
    ->forOptionalCategory($request->category)
    ->forOptionalBidRange($request->minimum_bid, $request->maximum_bid)
    ->forOptionalKeyword($request->keyword)
    ->paginate($pageSize);

我假设Listing模型与bids()模型之间存在Bid关系:

class Listing extends Model
{
    ...
    public function bids()
    {
        return $this->hasMany(\App\Bid::class);
    }

接下来,让我们添加将最高出价金额映射到模型的最重要范围:

public function scopeWithMaximumBidAmount($query)
{
    $bids = $this->bids();
    $bidsTable = $bids->getRelated()->getTable();
    $listingKey = $bids->getForeignKeyName();

    $bidsQuery = \App\Bid::select($listingKey)
        ->selectRaw('MAX(bid_amount) as bid_amount')
        ->groupBy($listingKey);

    $subquery = DB::raw("({$bidsQuery->toSql()}) " . $bidsTable);

    return $query->select($this->getTable() . '.*', 'bid_amount')
        ->join($subquery, function ($join) use ($bids) {
            $join->on(
                $bids->getQualifiedForeignKeyName(),
                '=',
                $bids->getQualifiedParentKeyName()
            );
        });
}

此实现使用模型和关系上的元数据来设置查询的表名和列,因此如果这些更改,我们不需要更新此方法。我们可以看到,我们首先构建一个子查询来加入,计算每个列表的最高出价。不幸的是,大部分内容都没有记录 - Laravel源代码是高级案例的必要参考。

上面的示例显示了我们如何为连接表提供原始SQL子查询,并传递闭包而不是列名来为连接添加任何专用子句。我们使用标准的Eloquent查询构建器创建子查询 - 因此我们可以根据需要安全地绑定参数 - 并使用toSql()方法将其转换为SQL字符串。此范围会为包含最高出价的调用返回的每个bid_amount添加Listing属性。

以下是剩余的查询范围。这些都是不言自明的:

public function scopeForOptionalKeyword($query, $keyword)
{
    if (empty($keyword)) {
        return $query;
    }

    return $query->where(function ($query) use ($keyword) {
        $query->where('name', 'LIKE', "%{$keyword}%")
            ->orWhere('tagline', 'LIKE', "%{$keyword}%")
            ->orWhere('short_description', 'LIKE', "%{$keyword}%")
            ->orWhere('seller_notes', 'LIKE', "%{$keyword}%");
    });
}

public function scopeForOptionalCategory($query, $category)
{
    if (empty($category)) {
        return $query;
    }

    return $query->where('category_id', $category);
}

public function scopeForOptionalBidRange($query, $minimum, $maximum)
{
    if (! empty($minimum)) {
        $query->where('bid_amount', '>=', $minimum);
    }

    if (! empty($maximum)) {
        $query->where('bid_amount', '<=', $maximum);
    }

    return $query;
}

当我们要匹配所需的SQL查询时,toSql()方法在构造复杂查询构建器对象时确实很有用。如果遇到性能问题,我们可能还想检查表中的索引。如果我们有资源,可能值得索引bids.bid_amount或调整关键字查找列。

答案 1 :(得分:1)

加入查询中使用 DB :: raw ,将最高 bid_amount 设为 bid_amount 即可。我在您的查询中做了一些更改,现在应该可以正常工作。

$listings = Listing::join(DB::raw('(select listing_id, Max(bid_amount) as bid_amount from bids GROUP BY listing_id) bids'),'bids.listing_id','listings.id');

    if( !empty($request->keyword) ) {
        $listings = $listings->where('listings.name','LIKE','%'.$request->keyword.'%')
            ->orWhere('listings.tagline','LIKE','%'.$request->keyword.'%')
            ->orWhere('listings.short_description','LIKE','%'.$request->keyword.'%')
            ->orWhere('listings.seller_notes','LIKE','%'.$request->keyword.'%');
    }

    if( !empty($request->category) ) {

        $listings = $listings->where('listings.category_id','=',$request->category);

    }

    if( !empty($request->minimum_bid) && !empty($request->maximum_bid) ) {

        $listings = $listings->whereBetween('bid_amount', [$request->minimum_bid, $request->maximum_bid]);

    } else {

        if( !empty($request->minimum_bid) ) {

            $listings = $listings->where('bid_amount', '>', $request->minimum_bid);

        }

        if( !empty($request->maximum_bid) ) {

            $listings = $listings->where('bid_amount', '<', $request->maximum_bid);
        }

    }

    return $listings->get();

希望有所帮助:)

答案 2 :(得分:0)

所以我离得太远了,这感觉很糟糕......如果有人可以建议如何改进,那就真的很有帮助。这很容易导致查询太多,而且无论如何它绝对不是最佳实践。我一直只是盯着该死的东西,以至于我无法看到更好/更清洁的方式。

    // Create an instance that we can refine.
    $listings = \App\Listing::select('*');

    // Check if the keyword search exists in any relevant column.
    if( !empty($request->keyword) ) {
      $listings = $listings->where('listings.name','LIKE','%'.$request->keyword.'%')
                  ->orWhere('listings.tagline','LIKE','%'.$request->keyword.'%')
                  ->orWhere('listings.short_description','LIKE','%'.$request->keyword.'%')
                  ->orWhere('listings.seller_notes','LIKE','%'.$request->keyword.'%');
    }

    // Check if the result is in the requested category.
    if( !empty($request->category) ) {
      $listings = $listings->where('listings.category_id','=',$request->category);
    }

    $listings = $listings->get();

    // Check for a greatest bid between the min and max.
    if( !empty($request->minimum_bid) || !empty($request->maximum_bid) ) {
      if( !empty($request->minimum_bid) ) {
        $listings = $listings->filter(function($listing) use( $request ){
          return \App\Bid::getCurrentHighestBid($listing->id) >= $request->minimum_bid;
        });
      }

      if( !empty($request->maximum_bid) ) {
        $listings = $listings->filter(function($listing) use( $request ){
          return \App\Bid::getCurrentHighestBid($listing->id) <= $request->maximum_bid;
        });
      }
    }

    // We're left with a collection, but we need to paginate the results.
    // We use the collection to perform a secondary query to get only the
    // relevant rows. *cough*hack*cough*
    $listings = \App\Listing::select('*')->whereIn('id', $listings->pluck('id'))->paginate(30);

    // Pass it on to the view.
    return view('listings.main', ['listings' => $listings]);

我真的希望这可以帮助某人,并且可以改进。 Laravel具有用户友好的文档,但似乎缺乏真正的深入参考,所以我不确定这些函数是否以正确的方式使用。

答案 3 :(得分:0)

如果我理解你是正确的,你想获取minimum_bid和maximum_bid之间的所有列表,并按listing_id对它们进行排序?

你可以这样做:

//This gets you array of collections sorted by listing_id and sorted from 
  highest bid_amount to lowest.
 $listings = Listing::join('bids', 'listings.id', '=', 'bids.listing_id');

if (!empty($request->minimum_bid)  && !empty($request->maximum_bid)) {

    $listings->whereBetween('bid_amount', [$request->minimum_bid,   
        $request->maximum_bid]);

} else {

    if( !empty($minimum_bid) ) {

        $listings->where('bid_amount', '>', $request->minimum_bid);

    }

    if( !empty($request->maximum_bid) ) {

       $listings=$listings->where('bid_amount', '<', $request->maximum_bid);
    }

}

$listings = $listings->get()
    ->sortByDesc('bid_amount')
    ->groupBy('listing_id');

结果看起来像这样(我测试了两个列表):

    Collection {#196 ▼
  #items: array:2 [▼
    2 => Collection {#184 ▼
      #items: array:3 [▼
        0 => Listing {#204 ▶}
        1 => Listing {#208 ▶}
        2 => Listing {#207 ▶}
      ]
    }
    1 => Collection {#197 ▼
      #items: array:4 [▼
        0 => Listing {#203 ▶}
        1 => Listing {#205 ▶}
        2 => Listing {#206 ▶}
        3 => Listing {#202 ▶}
      ]
    }
  ]
}