I have a table with categories, one table with products and another table products_user which tracks the products a user owns.
When displaying products on a page it should change a button from Buy to Bought if a user owns the product.
I get the products to display through $categories->products. What is the most efficient way to find out which of these products a user already owns?
I don't want to load the entire collection of the user owned products into the memory since these could be several thousands. I also don't want to create a Mysql query for each check.
There is an option for a wherein clause. But even then I am that there is a smarter way to create this clause without looping through every product to build an array.
Can someone help me to come up with a good logic? thank you
You can make use of Constraining Eager Loads to append more information to your products. In this case, the user_id is either NULL or user_id, meaning the user is bought the product or not.
$categories = Category::with(['products' => function ($q) {
$q->select(['products.*', 'user_id'])
->leftJoin('products_user', 'user_products.product_id', '=', 'products.id')
->where(function ($q) {
$q->whereNull('user_id')->orWhere('user_id', Auth::user()->id);
});
}])->get();
foreach ($categories as $category) {
$products = $category->products;
foreach ($products as $product) {
if (empty($product->user_id)) {
// user not yet bought the product
}
else {
// user already bought the product
}
}
}
Related
Let's say I have Category model with hasMany relation to Product model. Product has color column.
I want to return all categories with all colors that exists in this category, so I tried:
return Category::with(['products' => function ($query) {
$query->distinct('color');
}])->get();
Thanks to this I could later foreach or pluck category->products to get unique colors list. I know I can just get all products in every category, and then filter unique colors, but by doing this I would have to query for example 1000 products per category, instead just 5, which is unnecessary resource heavy. That's why I'm trying to do this on SQL level not PHP.
But this code does not work. There are no errors, it just still returns all products with duplicated colors. Why?
Edit:
Not sure why but my code works if I add select() with used columns before discrinct, and then distinct is making unique rows by all choosed columns. No "color" param required in distinct. Not sure why it works that way, need to dive deeper into SQL docs.
Have you tried this code? Somehow, this will reduce your unnecessary query.
$categories = Category::with([
'products'=> fn($q) => $q->select(['category_id', 'color'])->distinct('color')
])
->select('id') // select required column in categories table
->whereHas('products')
->get();
$colors = $categories->map(function($category) {
return $category->products->pluck('color');
})->toArray();
$color = [];
for ($i=0; $i < count($colors); $i++) {
$color = array_merge($color, $colors[$i]);
}
$uniqueColor = array_unique($color);
return $categories;
I've following data structure,
Product
id
name
description
Product Review
id
user_id
product_id
review
rating
status (true/false)
Relationship is one to many i.e. one product has many reviews.
What I want is to load all the products with verified reviews(i.e. reviews with status true)
I've tried following,
$products = Product::with('reviews')->get();
but when I iterate over $products to access reviews like $product->reviews, all the reviews(even status false reviews) are displayed.
Any kind of suggestion is appreciated.
You can add a closure to the with function to add extra filtering.
Product::with([
'reviews' => function ($query) { $query->where('status', true); }
])->get();
Another option is to add a filtered relation function to your product model.
public function verifyedReviews() {
return $this->reviews()->where('status', true);
}
And now call this function in your with clause.
Product::with('verifyedReviews')->get();
try this one by using clousre
$products = Product::with(['reviews' => fuction($query) {
return $query->where('status',true);
}])->get();
you can use WhereHas()
Your code would be:
$products = Product::whereHas('reviews',function($query){
return $query->where('status',true);
})->get();
I'm trying to access the parent value, the posts date, in a nested function.
In my application, a user has many posts, with each post being associated to a product (textbook). Each time someone views a page with the product, a new row is added to the product-views table.
I want to return the cumulative amount of times the users products have been seen. I've been able to get all the users posts, then the associated product, and then the count of all the views of that product.
Now I'd like to add another where() condition to only return views that have occured after the post was created. To do so, I need to get the posts date, e.g. views->product->post, while constructing the query like user->posts->product->views.
// getting all of the users available posts
$user = $request->user()->posts()->available()->with([
// with the textbook (product) associated with the post
'textbook' => function ($q) {
// get the count of textbook-page views
return $q->withCount([
// from the related table views
'views' => function ($q) {
// ===!!! Q !!!=== How do I access the posts (parent (grandparent?)) date, so that I only select the rows that have been viewed after the post was created ===!!!===
->where('date-viewed', '>=', 'posts.date');
},
]);
//some cleanup
}])->distinct()->get()->unique('isbn')->pluck('textbook.views_count')->sum();
How do I go backwards in a nested function to access the posts date?
It looks like as Jonas said in the comments, each with() relationship was a new query, so I ended up creating a hasManyThrough() relationship between the posts and views through the product.
// getting all of the users available posts
$user = $request->user()->posts()->available()->withCount([
'views' => function ($q) {
// ===!!! A !!!=== Fixed ===!!!===
->whereRaw('`date-viewed` >= posts.date');
},
])->distinct()->get()->unique('isbn')->pluck('views_count')->sum();
I have two tables 'purchases' and 'accounts_purchase_history'. purchase has many accounts history. So I need to get all the purchases with the latest history.
What i have done so far
$purchases = Purchases::with(['accounts_purchase_historie' => function($query){
return $query->orderBy('id','DESC')->first();
}])->orderBy('id','DESC')->where('status','extended')->get();
But this query only gets the first purchase history, How can i solve this?
You can use a HasOne relationship:
public function latest_history() {
return $this->hasOne(...)->orderBy('id','DESC');
}
$purchases = Purchases::with('latest_history')
->orderBy('id','DESC')
->where('status','extended')
->get();
foreach($purchases as $purchase) {
$purchase->latest_history
}
However, this will still fetch all the histories from the database in the background.
Take the following code snippet. I am loading the OrderDetail model with the relationship to Inventory:
// Load order details for live orders and inventory ids
$order_details = \OrderDetail::whereIn('order_id', $live_order_ids)
->whereIn('inventory_id', $inventory_ids)
->with('inventory')
->get();
Once the collection is loaded, I am doing the following processing:
// Deduct reserved stock from live un-picked orders
foreach ($order_details as $detail)
if (array_key_exists($detail->inventory->sku, $free_stocks))
$free_stocks[$detail->inventory->sku] -=
($detail->qty_ordered - $detail->qty_picked);
As you can see, from the loaded collection, I only need the following:
order_details.qty_ordered
order_details.qty_picked
inventory.sku
I'd like to optimise this and only load what I require, i.e. specify explcitly what columns I want to load from the main and related table.
How can I do this? When I tried the following, it doesn't seem to work:
// Load order details for live orders and inventory ids
$order_details = \OrderDetail::whereIn('order_id', $live_order_ids)
->whereIn('inventory_id', $inventory_ids)
->with('inventory')
->get(['qty_ordered','qty_picked','sku']);
// Load order details for live orders and inventory ids
$order_details = \OrderDetail::whereIn('order_id', $live_order_ids)
->whereIn('inventory_id', $inventory_ids)
->with(['inventory' => function($query) {
$query->addSelect(['id','sku']);
}])
->get(['qty_ordered','qty_picked']);
Any ideas?
You need to use select in query to get selected columns. Or if it will not work you need use laravel joins.
Also Please see select columns from queries in laravel official document.
http://laravel.com/docs/4.2/queries.
// Load order details for live orders and inventory ids
$order_details = \OrderDetail::whereIn('order_id', $live_order_ids)
->whereIn('inventory_id', $inventory_ids)
->with(['inventory' => function($query) {
$query->addSelect(['id','sku']);
}])
->select('qty_ordered','qty_picked')
->get();