2017-12-01 4 views
1

Ich habe zwei Tabellen: listings, die die Details eines Produkts enthält, und bids, die das Gebotsverlauf der Website enthält.Laravel Eloquenz mit dem Maximum in der zweiten Tabelle?

Für kurze Relevanz vorstellen, dass listings die folgenden Felder hat:, category_id, Tagline, short_description und seller_notes Name.

In bids haben wir zwei relevante Bereiche: listing_id und bid_amount.

Aus Gründen an anderer Stelle relevant, muss dies in Eloquent sein, wie ich Zugriff auf das Modell benötigen.

Das Problem scheint mit der MAX Gebotsbetrag Linie oder der Bewertung der bid_amount -kein ganz gleich, wie ich Ansatz, ich immer mit einem irrelevanten Ergebnis am Ende zu sein, aber es gibt keinen offensichtlichen Fehler, die ich sehen kann.

$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); 
    } 
} 

Die Suche ist Ergebnisse mit einem aktuellen Höchstgebot zwischen minimum_bid und maximum_bid (Feldern aus dem Suchfeld) zu finden. Das Problem besteht darin, dass es mehrere Einträge (listing_id) geben kann, die zwischen diesen Beträgen ein aktuelles Höchstgebot (MAX(bid_amount)) haben. Ich möchte die Einträge anzeigen, wo die MAX(bid_amount) für diese bid.listing_id zwischen minimum_bid und maximum_bid ist. Dies könnte möglicherweise zu mehreren Auflistungen führen.

Die äquivalente sollte MySQL-Abfrage sein:

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) 

Ich weiß, das ist etwas dumm, dass ich falsch, mache ich gerade wirklich einen frischen Satz Augen benutzen konnte. Vielen Dank für Ihre Hilfe.

+1

Hat die Liste bid_id? Ich bin verwirrt von Ihrer Tischstruktur. Sie treten der Listentabelle nach bids.id bei. 'listing.id = bids.id'? –

+0

Jetzt macht das Verbinden Sinn. Komm auf die nächste Frage. Was genau willst du?Beitritt basierend auf einem maximalen Gebotsbetrag pro Angebot oder Sie möchten einen einzelnen Datensatz mit einem maximalen Gebotsbetrag und Listing Details? –

+1

Sie möchten die MAX (bid_amount) -Liste von minimum_bid und maximum_bid Filter anzeigen, ist das korrekt? –

Antwort

2

Das Problem mit dem Code in der Frage hilft, ist, dass es eine Abfrage erstellt, die den maximalen Gebotsbetrag über all der Angebote zu finden versucht, wenn wir das wirklich brauchen max für jede Gebotsgruppe pro Eintrag. Werfen wir einen Blick darauf, wie man das mit Eloquent macht. Wir werden local query scopes verwenden, um die Logik zu kapseln und die API aufzuräumen, so dass wir es so nennen kann:

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

Ich gehe davon aus, dass das Listing Modell eine bids() Beziehung zum Bid Modell enthält:

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

Als nächstes wollen sie den wichtigsten Umfang hinzufügen, die den maximalen Gebotsbetrag zum Modell abbildet:

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() 
      ); 
     }); 
} 

Diese Umset tation verwendet Metadaten zu den Modellen und Relationen, um die Tabellennamen und Spalten für die Abfrage festzulegen, sodass diese Methode nicht aktualisiert werden muss, wenn sich diese ändern. Wie wir sehen können, bauen wir zuerst eine Unterabfrage auf, bei der das höchste Gebot für jeden Eintrag berechnet wird. Leider ist vieles davon nicht dokumentiert — Der Laravel-Quellcode ist eine notwendige Referenz für fortgeschrittene Fälle.

Das obige Beispiel zeigt, wie wir eine rohe SQL-Unterabfrage für die verbundene Tabelle bereitstellen und eine Closure anstelle von Spaltennamen übergeben können, um spezielle Klauseln für die Verknüpfung hinzuzufügen. Wir erstellen die Unterabfrage mit dem standardmäßigen Eloquent Query Builder —, so dass wir bei Bedarf Parameter sicher binden können — und konvertieren Sie es in die SQL-Zeichenfolge mit der toSql() Methode. Dieser Bereich fügt ein bid_amount-Attribut zu jedem Listing hinzu, der von dem Aufruf zurückgegeben wird, der das höchste Gebot enthält.

Hier sind die übrigen Abfragebereiche. Dies sind ziemlich selbsterklärend:

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; 
} 

Wenn wir eine gewünschte SQL-Abfrage zu entsprechen haben, die toSql() Methode wirklich hilft, wenn ein komplexes Query Builder-Objekt zu konstruieren. Vielleicht möchten wir auch die Indizes in unseren Tabellen überprüfen, wenn es zu Leistungsproblemen kommt. Es lohnt sich vielleicht, bids.bid_amount zu indizieren oder Keyword-Lookup-Spalten zu optimieren, wenn wir die Ressourcen haben.

+1

Das ist nur verdammt elegant ... Danke. Der Versuch, Bereiche aus der Dokumentation herauszufinden, brachte mich dazu, meinen Kopf in einen Mixer zu stecken. Vielen Dank, weil das Setup, das ich hatte, unseren Server bis zum Monat 6 versenken würde. – stslavik

+1

@stslavik Gern geschehen! Übrigens habe ich den letzten Bereich aktualisiert. "BETWEEN" wird nicht benötigt - es ist äquivalent, wenn sowohl weniger als auch größer als vorhanden sind. Auch "BETWEEN" ist * inklusive *, also habe ich die Operatoren so verändert, dass sie sich ähnlich verhalten. –

0

So war ich nicht sehr weit weg, und das fühlt sich ziemlich hacky ... Wenn jemand beraten kann, wie man sich verbessert, wäre es wirklich hilfreich. Dies könnte leicht zu viel zu viele Anfragen führen, und es ist definitiv keine Best Practice. Ich habe das verdammte Ding nur so lange angeguckt, dass ich keinen besseren/saubereren Weg sehe, es zu tun.

// 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]); 

Ich hoffe wirklich, das jemand helfen kann, und dass es verbessert werden könnte. Laravel hat eine benutzerfreundliche Dokumentation, aber es scheint mir eine wirklich ausführliche Referenz zu fehlen, so dass ich nie ganz sicher bin, ob die Funktionen richtig verwendet werden.

0

Wenn ich Sie richtig verstanden habe, möchten Sie alle Auflistungen zwischen minimum_bid und maximum_bid greifen, und sortieren sie nach listing_id?

Sie können dies tun:

//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'); 

Ergebnis sieht wie folgt aus (i mit zwei Listings getestet):

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 ▶} 
     ] 
    } 
    ] 
} 
+0

Dies war eine der ersten Möglichkeiten, wie ich es angegangen bin, aber leider, da es viele Gebote für jeden Eintrag gibt, war das Rückgabeset alle Angebote für die Angebote. Wenn dann das Maximum genommen wird, um das höchste Ergebnis zu erhalten, wird nur das eine Ergebnis mit dem höchsten Gebot zurückgegeben. Trotzdem danke! – stslavik

1

Verwenden DB :: raw in Ihrem beitreten Abfrage zu erhalten die Maxbid_amount als bid_amount. Ich habe einige Änderungen an Ihrer Anfrage vorgenommen und es sollte jetzt funktionieren.

$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(); 

Hope it :)

Verwandte Themen