Eloquent: Scope for Model where latest related Model meets permission - laravel

I have a Model Products that has many of the Model Prices.
Every day there is a different price for the product.
Now I am trying to create a scope that gives me all products which latest price is between two values.
I tried this with a whereHas query:
public function scopePriceBetween($query, ...$priceRange) {
return $query->whereHas('price', function ($query) use ($priceRange) {
$query->latestPrice()->whereBetween('price', $priceRange);
})
}
with the scope on the price model
public function scopeLatestPrice($query) {
return $query->latest('date')->limit(1);
}
But this will give me all the products where any price was between the range and not just the latest price.
Is there a way to do this with acceptable performance in eloquent or do I need to add a latest_price column to my product model?

for later price you can use database temp column or you can use redis. but i recommend temp column.
First Solution : Temporary Table
DB::statement("CREATE TEMPORARY TABLE last_prices SELECT prices.* from prices join products on products.id=prices.product_id and prices.id=(select id from prices where prices.product_id=products.id and `prices`.`deleted_at` is null order by `id` desc limit 1);");
$query = Product::select("products.*")
->join("last_prices", "products.id", "last_prices.product_id");
in this example, every task has many jobs, you can query database to make a temporary table and fetch the last_job from jobs;
Second Solution : Using Cache Server
DBMS temp table is fast, but you can gain performance by Cache server (for example redis).
you can store every product last price in cache server by product_id:
public function getLastPriceAttribute(){
//cache for an hour
$p_id = $this->id;
return Cache::tags(['product'])->remember($this->id, 60*60, function () uses ($p_id) {
return Price::where('product_id', $p_id)
->latest()
->first();
});
}
The third solution:
if your price updates are daily and you haven't or don't want to use cache server you can make a database table named last_prices and update it daily with laravel schedule as follow:
in App\Console\Kernel.php :
//suggestion has not tested
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
$updateValues = array();
foreach( Product::all() as $product){
array_push($updateValues , array(
"product_id" => product->id,
"price_value" =>
Price::where('product_id',$product->id)
->latest()
->first()->price_value;
));
}
LastPrices::updateOrInsert($updateValues);
})->dailyAt("05:30"); }
UPDATE
for this:
Product::latestPriceBetween([100,200])->category('electronics');
you can make Suggested Third Solution to have Last_price Table.
and define scope with join, with this nice package : https://github.com/fico7489/laravel-eloquent-join
looks like something like this:
public function scopePriceBetween($query, ...$priceRange) {
return $query->join("last_prices","last_prices.product_id","products.id")->whereBetween('last_prices.value', $priceRange);
}

Related

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

Get only one column from relation

I have found this: Get Specific Columns Using “With()” Function in Laravel Eloquent
but nothing from there did not help.
I have users table, columns: id , name , supplier_id. Table suppliers with columns: id, name.
When I call relation from Model or use eager constraints, relation is empty. When I comment(remove) constraint select(['id']) - results are present, but with all users fields.
$query = Supplier::with(['test_staff_id_only' => function ($query) {
//$query->where('id',8); // works only for testing https://laravel.com/docs/6.x/eloquent-relationships#constraining-eager-loads
// option 1
$query->select(['id']); // not working , no results in // "test_staff_id_only": []
// option 2
//$query->raw('select id from users'); // results with all fields from users table
}])->first();
return $query;
In Supplier model:
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id')
//option 3 - if enabled, no results in this relation
->select(['id']);// also tried: ->selectRaw('users.id as uid from users') and ->select('users.id')
}
How can I select only id from users?
in you relation remove select(['id'])
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id');
}
now in your code:
$query = Supplier::with(['test_staff_id_only:id,supplier_id'])->first();
There's a pretty simple answer actually. Define your relationship as:
public function users(){
return $this->hasMany(User::class, 'supplier_id', 'id');
}
Now, if you call Supplier::with('users')->get(), you'll get a list of all suppliers with their users, which is close, but a bit bloated. To limit the columns returned in the relationship, use the : modifier:
$suppliersWithUserIds = Supplier::with('users:id')->get();
Now, you will have a list of Supplier models, and each $supplier->users value will only contain the ID.

Laravel Fetching one to Many Relationship

Hello I am learning laravel and I am having an issue retrieving data from my relations.
In my database there are Product and Groups filled with dummy data.
I defined my relationship like this in product model:
public function Group()
{
return $this->hasMany('App\Groups','product_id', 'id');
}
And in my group vice versa with :
public function Product()
{
return $this->belongsTo('App\Product','product_id', 'id');
}
The way I am referencing to my products table is :
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
Now I have column product_id in my database under groups, and it is linked to if from products id it seems.
The groups table contains of its auto incremented id and product_id foreign key column.
While products table has auto incremented id and name column.
The issue is here :
How do I return the products that are not null or have value (of products id) in groups table.
I tried something like this in my filter controller:
public function getProductsWithGroup()
{
$Products = Product::with('groups')->get();
return $Products ;
}
But that is giving me call to undefined relations.
I am not sure how to access belongsTo or hasMany methods and whether I need an extra group_id column in my products table.
You named the relationship wrong. It should be groups & define in lowercase as
public function groups()
{
return $this->hasMany('App\Groups','product_id', 'id');
}
And use ->has() to check existence
public function getProductsWithGroup()
{
$Products = Product::has('groups')->get();
return $Products ;
}
->with() is used to eager load and ->has() is used to check existence & filter.
To get the products don't have any groups,
$Products = Product::doesntHave('groups')->get();
To see other ways to use ->has() check, https://laravel.com/docs/5.7/eloquent-relationships#querying-relationship-existence

Laravel, How to retrieve parent records with certain Pivot table values belongsToMany

How can I retrieve all records of my model based on certain ID's in my pivot table?
I have the following 3 tables
users;
id,
name
stats;
id,
name
stats_selected;
user_id,
stats_id
Model
User.php
public function stats()
{
return $this->belongsToMany('App\stats', 'stats_selected', 'user_id', 'stats_id')->withTimestamps();
}
Controller
// Get all users with example of stats ID's
$aFilterWithStatsIDs = [1,10,13];
$oUser = User::with(['stats' => function ($query) use($aFilterWithStatsIDs ) {
$query->whereIn('stats_id', $aFilterWithStatsIDs);
}])
->orderBy('name', 'desc')
->get()
This outputs just all the users. Btw, fetching users with there stats and saving those selected stats into the DB is not a problem. That works fine with the above lines.
But how do I retrieve only the users which has certain stats_id's within them?
But how do I retrieve only the users which has certain stats_id's within them?
Use a whereHas conditional.
User::whereHas('stats', function ($stats) use ($aFilterWithStatsIDs) {
$stats->whereIn('id', $aFilterWithStatsIDs);
});

How to connect three tables in Laravel?

I have thee tables: Orders, Products, Products_images.
I need to get ll orders, for this I do:
$orders = Order::with("products")->where("user_id", Auth::guard('api')->user()->id)->orderBy('id')->get();
Where with("products") is function in model Order
public function products()
{
return $this->hasOne("App\Product", "id", "product_id");
}
So, I have connected two table. Also I need connect with table Products with table Products_images in this query.
How can I do this?
Add a product_images function to the Products Model
public function product_images() { return $this->hasMany("App\ProductImage");}
Modify the above line's App\ProductImage to reflect the model for your table. Then you can access all the product images records that belongs to your products by doing the following:
$orders = Order::with("products.product_images")->where("user_id", Auth::guard('api')->user()->id)->orderBy('id')->get();
Look under nested eager loading on this link : https://laravel.com/docs/5.3/eloquent-relationships#eager-loading

Resources