Obteniendo el último valor en una mesa unida con Eloquent

Tengo dos tablas como esta:

productos:

+----+-----------+ | id | name | +----+-----------+ | 1 | Product 1 | | 2 | Product 2 | | 3 | Product 3 | | 4 | Product 4 | +----+-----------+ 

precios:

 +----+-------+------------+---------------------+ | id | price | product_id | created_at | +----+-------+------------+---------------------+ | 1 | 20 | 1 | 2014-06-21 16:00:00 | | 2 | 10 | 1 | 2014-06-21 17:00:00 | | 3 | 50 | 2 | 2014-06-21 18:00:00 | | 4 | 40 | 2 | 2014-06-21 19:00:00 | +----+-------+------------+---------------------+ 

Tengo esta relación en el Producto:

 public function prices() { return $this->hasMany('Price'); } 

Puedo ejecutar fácilmente Product::with('prices')->get(); para obtener cada producto con cada uno de los precios que tiene.

¿Cómo puedo usar Eloquent para obtener solo el precio más reciente? (Además, ¿qué ocurre si quiero el precio más barato / más caro en su lugar?)

Puede ajustar sus relaciones para obtener lo que quiere. La respuesta aceptada, por supuesto, funciona, sin embargo, puede ser memoria excesiva con una gran cantidad de datos.

Descubra más aquí y allá .

A continuación, le mostramos cómo usar Eloquent para esto:

 // Product model public function latestPrice() { return $this->hasOne('Price')->latest(); } // now we're fetching only single row, thus create single object, per product: $products = Product::with('latestPrice')->get(); $products->first()->latestPrice; // Price model 

Eso es bueno, pero hay más. Imagine que desea cargar el precio más alto (solo un valor) para todos los productos:

 public function highestPrice() { return $this->hasOne('Price') ->selectRaw('product_id, max(price) as aggregate') ->groupBy('product_id'); } 

Aún no es muy conveniente:

 $products = Product::with('highestPrice')->get(); $products->first()->highestPrice; // Price model, but only with 2 properties $products->first()->highestPrice->aggregate; // highest price we need 

Por lo tanto, agregue este accesorio para hacer la vida más fácil:

 public function getHighestPriceAttribute() { if ( ! array_key_exists('highestPrice', $this->relations)) $this->load('highestPrice'); $related = $this->getRelation('highestPrice'); return ($related) ? $related->aggregate : null; } // now it's getting pretty simple $products->first()->highestPrice; // highest price value we need 

Cuando Laravel impaciente carga una relación, realizará dos consultas similares a esta:

 SELECT * FROM products WHERE 1; SELECT * FROM prices WHERE product_id = 1; 

Lo que quiere hacer es agregar una condición a la segunda consulta para obtener la fila con el precio más reciente. Entonces querrías algo como esto:

 SELECT * FROM products WHERE 1; SELECT * FROM prices WHERE product_id = 1 ORDER BY price; 

Por suerte en las Restricciones de carga impaciente de Laravel puede, en lugar de pasar una cadena en with() , puede pasar una matriz con el nombre de la relación como clave y un cierre de subconsulta como su valor. Me gusta esto:

 $products = Product::with(array('prices' => function($query) { $query->orderBy('created_at', 'desc'); }))->get(); 

Luego en tu código puedes hacer:

 $product->prices->first(); 

para obtener el precio más reciente de cada producto.

Nota: Puede notar que Laravel aún cargará todos los precios de cada producto. No creo que haya una forma de evitarlo mientras sigo usando puramente Eloquent porque la forma en que el trabajo de carga ansioso es obtener todos los registros de relación en una sola consulta, por lo que no es una manera fácil de decir obtener solo el precio más reciente para cada uno producto.


Otra solución:

Sin embargo, si estrictamente necesita saber solo un valor de otra tabla, podría hacer una selección secundaria en su lugar:

 $products = Product::select('*') ->addSelect(DB::raw('(SELECT price FROM prices WHERE products.id = prices.product_id ORDER BY created_at DESC LIMIT 1) price')) ->get();