web-dev-qa-db-fra.com

Laravel Tri éloquent par colonne de table de relation

J'ai essayé de trier les produits de shop_products table par pinned colonne de shop_products_options table:

$products = Shop\Product::with(['options' => function ($query) {

    $query->orderBy('pinned', 'desc'); 

}])->paginate(5);

Je mets la relation dans Shop\Product model:

public function options()
{
    return $this->hasOne('Shop\Options');
}

Mais les produits ne sont pas triés. Je reçois une requête qui ne fonctionne qu'avec shop_products_options table.

SELECT * FROM `shop_products_options` WHERE `shop_products_options`.`product_id` in ('8', '9', '10', '11', '12') ORDER BY `pinned` DESC

Comment le réparer?

30
alexk

Le chargement désireux utilise des requêtes distinctes, vous devez donc vous joindre à cela:

$products = Shop\Product::join('shop_products_options as po', 'po.product_id', '=', 'products.id')
   ->orderBy('po.pinned', 'desc')
   ->select('products.*')       // just to avoid fetching anything from joined table
   ->with('options')         // if you need options data anyway
   ->paginate(5);

La clause SELECT est là pour ne pas ajouter de colonnes jointes à votre modèle Product.


modifier: selon le commentaire @alexw - vous pouvez toujours inclure des colonnes de tables jointes si vous en avez besoin. Vous pouvez les ajouter à select ou appeler addSelect/selectRaw etc.

68
Jarek Tkaczyk

Vous ne pouvez pas trier par colonne de table associée sans joindre manuellement la table associée. La réponse de Jarek est correcte mais cela pourrait être vraiment gênant:

1.Le premier problème est que vous devez vous soucier de la sélection.

->select('products.*')

raison: sans select () id de shop_products_options peut être sélectionné et hydraté dans le modèle de produit.

2.Le deuxième problème est que vous devez vous soucier de groupBy.

->groupBy('products .id');

raison: si la relation est HasOne et qu'il existe plusieurs options shop_products_options pour le produit, la requête renvoie plus de lignes pour les produits.

3.Le troisième problème est que vous devez modifier toutes les autres clauses where de:

->where('date', $date)

à

->where('products .date', $date)

raison: les produits et shop_products_options peuvent tous deux avoir l'attribut "date" et dans ce cas, sans sélectionner l'attribut avec la table "colonne ambiguë", une erreur sera levée.

4.Le quatrième problème est que vous utilisez des noms de table (pas des modèles), ce qui est également mauvais et gênant.

->where('products.date', $date)

5.Le cinquième problème est que vous devez vous soucier des suppressions logicielles pour les tables jointes. Si le shop_products_options utilise le trait SoftDeletes, vous devez ajouter:

->where('shop_products_options .deleted_at', '=', null)

Tous les problèmes ci-dessus sont très frustrants et rejoindre des tables avec éloquent peut introduire de nombreux problèmes dans votre code. J'ai créé un package qui s'occupe de tous les problèmes ci-dessus et vous pouvez trier par attribut de relation de manière élégante, pour votre cas ce serait:

$products = Shop\Product::orderByJoin('options.pinned', 'desc')->paginate(5);

Pour plus d'informations, voir https://github.com/fico7489/laravel-eloquent-join

10
fico7489