Laravel query hasMany where rows with custom values - laravel

I have following tables in my DB:
TABLE_PRODUCTS
id
name
TABLE_VARIANTS
id
product_id
sku
TABLE_VARIANTS_PROPERTIES
id
variant_id
property_value_id
I'm trying to build query for filtering products. Let's say I want to show only that products which have variants with property_value_id=1 and property_value_id=2
Relations: PRODUCTS -> hasMany -> VARIANTS -> hasMany -> VARIANTS_PROPERTIES.
Any ideas?
UPDATE:
I used Alexandr Peters answer. Here is my code:
$query->whereHas('variants.properties.propertiesValues', function ($query) use ($variants) {
$query->whereIn('value', $variants);
});
To be more specific I have one more table in my query which I didn't specify before. This query works but not as expected. What I want to do is:
Get all PRODUCTS which have PRODUCTS_VARIANTS which have PRODUCTS_VARIANTS_PROPERTIES which have PRODUCTS_PROPERTIES_VALUES as active filter (VARIANTS). I know all variants - I'm using cartesian product for this.
So for example I need to get all products which have variants which have all values from variant in filter. This query works only if there are variants only with 1 value. How can I solve this? I'm trying with foreach($variants as ...) but no luck. Still not working 100% as expected.
TABLES:
TABLE_PRODUCTS
id
name
TABLE_PRODUCTS_VARIANTS
id
product_id
sku
TABLE_PRODUCTS_VARIANTS_PROPERTIES
id
variant_id
property_value_id
TABLE_PRODUCTS_PROPERTIES_VALUES
id
value
All possible variants from active filter are in nested array for example I have active filter with color BLACK and RED and size filter XS:
[
0 => [
0 => Black
1 => XS
]
1 => [
0 => Red
1 => XS
]
]
I need to check values from VARIANTS array in last table TABLE_PRODUCTS_PROPERTIES_VALUES in column value. But it needs to have both values (Black & XS)
Any ideas?
UPDATE - SOLVED
Ok I finally solved it. Here is my code:
$query->whereHas('variants', function ($query) use ($variants) {
$i = 1;
foreach($variants as $variant)
{
if($i == 1) {
$query->whereHas('properties', function($query) use ($variant) {
$query->whereHas('propertiesValues', function($query) use ($variant) {
$query->whereIn('value', $variant);
});
}, '=', count($variant));
} else {
$query->orWhereHas('properties', function($query) use ($variant) {
$query->whereHas('propertiesValues', function($query) use ($variant) {
$query->whereIn('value', $variant);
});
}, '=', count($variant));
}
$i++;
}
});
Any ideas how to make it less complicated? :)

Nested has or whereHas statements may also be constructed using "dot" notation. For example, you may retrieve all posts that have at least one comment and vote. So if you want to query nested variants_properties from products you have to put it with dots 'relation1.relation2' in whereHas
$ids = [1,2];
$products = Product::whereHas('variants.variants_properties', function ($query) use ($ids) {
$query->whereIn('property_value_id', $ids);
})->get();

Related

Data filtering query in Laravel many-to-many relationship

there is a table named products and another table named features that have a many-to-many relationship.
I want to write a query that returns products that, for example, have features 2, 3, and 11 at the same time. Now I wrote the following command
$params = [2,3,11];
$products = Product::with('photos')
->whereHas('features', function ($q) use ($params) {
$q->whereIn('feature_id' , $params);
})
->whereIn('category_id', $ids)
->orderByRaw($sortBy)
->paginate($paginate);
when I use whereIn, it works in the form of OR and shows me products that either have the feature 2 or 3 or 11..
I want it to be in the form of AND, which means it will only find products that have all the desired features together
$products = Product::with('photos')
->whereHas('features', function ($q) use ($params) {
foreach($params as $p) {
$q->where('feature_id', $p);
}
})
->whereIn('category_id', $ids)
->orderByRaw($sortBy)
->paginate($paginate);
and this query that I wrote returns empty data.

Laravel Eloquent with() selecting specific column doesn't return results

Say I have 2 models, Category and POI where 1 Category can have many POIs.
$categoryDetails = Category::with([
'pois' => function ($query) {
$query->where('is_poi_enabled', true);
},
])->findOrFail($id);
The above query returns results from the specific Category as well as its POIs.
However, with the query below:
$query->select('id', 'name')->where('is_poi_enabled', true);
The POIs become empty in the collection.
Any idea why this is happening? When added a select clause to the Eloquent ORM?
While doing a select it's required to fetch the Relationship local or Primary key.
For an example POIs table contains category_id then it's required to select it
Try this:
$categoryDetails = Category::with([
'pois' => function ($query) {
$query->select(['id', 'category_id', 'is_poi_enabled'])
->where('is_poi_enabled', true);
},
])->findOrFail($id);
Good luck!

Laravel : orWhere Search query

I have a simple query in my model to get data. Now I want to search with companyname.My query code:
$searchablePost = Post::with(['product','postattribute.attribute.category','user.userDetails'])
->whereIn('status',$is_or_active)
->whereIn('product_id', $userApprovalProductIDs)
->whereIn('demand_or_supply', $is_demand_supply)
->offset($offset)->limit($limit)
->orderBy('id','desc');
Now, I got 6 rows in this 6 rows I want to filter data with companyname which i get with comma separated abcCompany,indCompany.
array:2 [
0 => "abccompany"
1 => "indcompany"
]
What I try :
if($companyname !="") {
$companyDetail = Explode(',',$companyname);
$searchablePost->whereHas('user.userDetails', function ($query) use ($companyDetail) {
$i=1;
foreach ($companyDetail as $search_with_compayname) {
if(count($companyDetail)>0 && $i==1) {
$query->where('company','LIKE',"%{$search_with_compayname}%");
} else {
$query->orWhere('company','LIKE',"%{$search_with_compayname}%");
}
$i++;
}
});
}
Is it good or is there any other way to good search ?
If you want to search a CSV list of company names, MySQL makes available a function FIND_IN_SET. I think you can just add a whereRaw clause to your current Laravel query:
$searchablePost = Post::with(['product','postattribute.attribute.category','user.userDetails'])
->whereIn('status', $is_or_active)
->whereIn('product_id', $userApprovalProductIDs)
->whereIn('demand_or_supply', $is_demand_supply)
->whereRaw('FIND_IN_SET(company, ?) > 0', [$search_with_compayname])
->offset($offset)
->limit($limit)
->orderBy('id', 'desc');
While the above may work, the best long term approach is to avoid CSV and other unnormalized data in your database. Ideally, you would have a table containing the list of company names, with one name on each record. Then, you could join or use an EXISTS query to answer your question.

Laravel eloquent get order with current status using pivot table

I have a situation to access orders have current status: slug= "new"
Here is the scenario:
Table Statuses: with cols [id,title,slug]
Pivot Order_Status: with cols [id, order_id,status_id]
Table Orders: with cols [id, priority,client_id,...]
Where,
1 order can have many statuses
Now I want to get all the orders where the last status of that order
is "new"
I am not clear if I should use the status model to get all order or get orders and apply scope.
$status = Status::where('slug','new')->first();
$orders = $status->orders()->?
or should do something like this:
$status = Status::where('slug','new')->first();
$orders = Order::withCurrentStatus($status->id)->?
? means I don't know how to complete this also what should be in scope function.
I have to use eloquent not custom raw query.
I shall be thankful if someone can guide me here.
You can define a condition on your eager loading query
$statuses = Status::where('slug', 'new')
->with([
"orders" => function ($query) {
$query->where('some_column', '=', 'possible-value'); // single condition
//or you can also pass array on ->where()
}
])->get();
// dd($statuses); -> you can then loop through statuses and get each
// orders by $status->orders
If you don't have any conditions on your orders, you just simply eager load it
$statuses = Status::where('slug', 'new')->with('orders')->get();

Constraining eager loaded relationship

I found a very bizarre behavior of with function for overloading relationships. I have Product and Deal relationships, such that Product belongsTo() Deal (through product_id in deals table). Now, when I try to get all products on sale:
Product::with(['deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
}])->get()
this returns a collection of all products, even though there are no records in deals table and all products have deal_id set to NULL. At the same time Product::has('deal')->get() returns an empty collection, as you would expect.
I initially discovered this problem while trying to fetch five random products on sale together with Deal and Image relationships:
Product::with(['deal' => function ($query) {
$query->whereDate('ends_at', '>', // promo still active
Carbon::now()->toDateTimeString());
},
'images' => function ($query) {
$query->where('featured', true); // image featured on homepage
}])
->where('status', 'IN_STOCK') // 'In Stock'
->whereNull('deleted_at') // wasn't soft-deleted
->orderByRaw('RAND()')
->take(5)->get())
This yields a collection with 5 random Products out of all Products. I tried with query->whereNotNull('ends_at')->whereDate('ends_at' ..... ); but got same results.
What am I doing wrong here?
Your understanding of the concept is completely wrong here.
If you are saying that a Product belongsTo() Deal, then lets assume that a Deal hasMany() Products.
This is the deals table
deals
id | name | ends_at | blah | blah
products
id | deal_id | name | blah | blah
So basically, the Product::with('deal') should return you all products with their Deals being Eager loaded. But Deal::with('products') will return you an empty collection, since no products have a valid deal_id in it.
It is important to note that, since Product can only belongTo a single Deal, you will always get the Deal Model rather than a collection when you perform Product::with('deal') query. But when you perform Deal::with('products') you are bound to get a collection.
So basically, when you say
This returns a collection of all products, even though there are no records in deals table and all products have deal_id set to NULL.
It is pretty obvious.... because the query here is being done on Products and not Deal. If you are trying to find the Deal where ends_at > Carbon::now(), you'll have to do this.
Deal::with('product')->where('ends_at', '>', Carbon::now()->toDateTimeString())
When you use with then it only eager loads the relations on the constraints provided but if you want to filter the parent model by their relations then whereHas is your friend. So your query should be as:
Product::whereHas('deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
})->get();
Now it will fetch only those Product which satisfy the given constraint.
You can also use the combination of with and whereHas as:
Product::whereHas('deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
})
->with(['deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
}])
->get();

Resources