Eloquent query distinct sub id - laravel

I have these models
I want to make a query that shows me all the products whose stock quantity> 0 and that does not repeat the products.
My query:
$stock_products_limit = Stock::distinct('product_id')->where('quantity', '!=', 0)->get();
This would be much easier using a size chart relating it to stocks ... but for now I don't have it
I need the model to return me, and then do a foreach:
#foreach($stock_products_limit as $stock_product)
#foreach($stock_product->product->product_images as $i=>$product_image)
...
#endforeach
...
#enforeach
In my models I have the hasMany and belongsTo relations made
How could I make the query? I've been trying the distinct, group by ... but nothing works for me. It only removes the ones with quantity 0 and repeats the product ID ...
Example of the query I want:
SELECT DISTINCT(stocks.product_id)
FROM stocks
INNER JOIN products ON stocks.product_id = products.id
WHERE quantity != 0
ORDER BY product_id
LIMIT 10;
Another example query (but LIMIT doesn't work with IN)
SELECT * from products where id in (SELECT DISTINCT(product_id)
FROM stocks
INNER JOIN products ON stocks.product_id = products.id
WHERE quantity != 0
ORDER BY product_id)

Instead of making the Stock model as the starting point, you might want to use the Product model. Then you don't even have to think about using DISTINCT. Let's use whereHas
return Product::whereHas('stocks', function ($query) {
$query->where('quantity', '>', 0);
})
->limit(10)
->get();

Related

select most selled products in range with one query?

Is it possible to select most selled products in range with one query?
Here tables :
orders table with columns(id,created_at,paid)
order_products(order_id,product_id)
products(id,title)
I tried this but it isn't working
\App\Models\Order::with('order_products')->where('paid',1)
->whereBetween('created_at',[$request->to,$request->from])
->orderBy(\DB::raw('count(order_products.product_id)'))->get());
presuming every order_products represent selling one product item, you could do it using left join and traditional group by:
$mostSalledPrducts = Product::query()
->leftJoin('order_products','products.id','=','order_products.product_id')
->join('orders','orders.id','=','order_products.order_id')
->where('orders.paid',1)
->selectRaw('products.*, COALESCE(count(order_products.id),0) total')
->groupBy('products.id')
->orderBy('total','desc')
->take(5)
->get();

laravel 8 use orderBy and withMax for order tables with another table

i want to orderby products with max price from details table.
products one to many relation with details table.
in laravel documentation we have aggregate methods like withMax ,withMin and withCount
. These methods will place a {relation}_{function}_{column} attribute on your resulting models.
now we only need to orderBy with this results.
$products = Product::withMax('details', 'price')
->orderBy('details_max_price' , 'desc')
->paginate(15);

Is there a way to rewrite this raw SQL query with Laravel Query Builder

I have two tables:
products
id | code | supplier_id |...
company_product
id | retail_price | copmany_id |...
There are multiple online shops from where I'm pulling the products via their API's which I store locally. It is pretty much used to compare their prices, unit stock, and so on which can be done via product code which is the same across all shops.
I'm fetching all products only once and updating them a couple of times daily. Other companies using this, let's say "platform", have different prices for products based on their contracts which are kept in the second table.
The thing I'm trying to achieve is to list all products but only the cheapest version of the product for that product code.
I am able to achieve it with the following query.
$products = DB::select(DB::raw(
"select p.*, t.price_min
from (select MAX(p.id) as id, p.code, MIN(cp.retail_price) as price_min
from products as p
left join company_product as cp on cp.product_id = p.id
group by p.code) as t
left join products as p on p.id = t.id"
));
Which gives me the result I want. However, I have lots of filters, sorts, relations, and pagination to add on top of this, which is the reason why I'm trying to rewrite it.
Every suggestion is much appreciated.
First of all, the raw MySQL query you probably want here is just an inner join:
SELECT p.*, t.price_min
FROM products p
INNER JOIN
(
SELECT p.code, MAX(p.id) AS max_id, MIN(cp.retail_price) AS price_min
FROM products p
INNER JOIN company_product cp ON cp.product_id = p.id
GROUP BY p.code
) t
ON p.id = t.max_id
As for the Laravel code, we can try creating the subquery in a separate variable, and then join to it:
$subquery = DB::table('products AS p')
->select([
'p.code',
DB::raw('MAX(p.id) AS max_id, MIN(cp.retail_price) AS price_min')])
->join('company_product AS cp', 'cp.product_id', '=', 'p.id')
->groupBy('p.code');
$query = DB::table('products AS p')
->select('p.*', 't.price_min')
->innerJoinSub($subquery, 't', function($join) {
$join->on('p.id', '=', 't.max_id');
})
->get();

Eloquent methods lumen

I have belongsToMany relationship between items and vehicle.
items can be assigned to multiple vehicles. same vehicle can b assigned to multiple items. so my pivot table item_vehicle have extra column date which will show that when vehicle is assigned to item.
here is my query.
select `items`.`id`, `items`.`name`, `items`.`area` as `total_area`,
`item_vehicle`.`date`, `vehicles`.`name` as `vehicle_name`,
SUM(parcel_vehicle.area) as processed_area
from `parcels`
inner join `item_vehicle` on `item_vehicle`.`p_id` = `items`.`id`
inner join `vehicles` on `item_vehicle`.`t_id` = `vehicles`.`id`
where `item_vehicle`.`date` < '?' and `items`.`processed` = ? and `vehicles`.`name`=?
group by items.id
what will be the eloquent way of doing this
Item::with(['vehicle'=>function($q){$q->wherePivot('date','<','2019/2/12');}])->whereHas('vehicle',function($q){$q->where('vehicles.id','2');})->where('processed',1)->where('id',4)
->get();
my concerns is it should run only one query
$parcels = Parcel::join('item_vehicle', 'item_vehicle.pid', '=' ,'items.id')
->join('vehicles', 'vehicles.id', '=' ,'item_vehicle.t_id')
->where('item_vehicle.date', '<', $date)
->where('items.processed', $processed)
->where('vehicles.name', $vehicleName)
->select(
'items.id',
'items.name',
\DB::raw('items.area as total_area'),
'item_vehicle.date',
\DB::raw('vehicles.name as vehicle_name'),
\DB::raw('SUM(parcel_vehicle.area) as processed_area')
)
->groupBy('items.id')
->get();
However, you have non-aggregated columns in select and you are doing group by. To make this work you might need to disable mysql's only_full_group_by mode

Laravel: Improved pivot query

I am successfully querying following and it create 130 queries, I want to optimise it and reduce the number of queries, I have set upped the model and controllers following way.
Post Modal
class Post extends Eloquent {
public function Categories () {
return $this->belongsToMany('Category', 'category_post');
}
}
Category Modal
class Category extends Eloquent {
public function posts () {
return $this->belongsToMany('Post', 'category_post');
}
}
and in the Controller, I am using following query, what following query does is, querying the results based on category id.
$category = Category::with('posts')->where('id','=',$id)->paginate(10)->first();
return Response::json(array('category' => $category));
If anyone can give me a hand to optimise the query, would be really greatful.
You are wrong, it doesn't create 130 queries.
It will create the following 3 queries:
select count(*) as aggregate from `categories` where `id` = '5';
select * from `categories` where `id` = '5' limit 10 offset 0;
select `posts`.*, `posts_categories`.`category_id` as `pivot_category_id`, `posts_categories`.`post_id` as `pivot_post_id` from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` in ('5');
But the question is what exactly you want to paginate. Now you paginate categories and it doesn't make much sense because there's only one category with selected $id.
What you probably want to get is:
$category = Category::where('id','=',$id)->first();
$posts = $category->posts()->paginate(10);
and this will again create 3 queries:
select * from `categories` where `id` = '5' limit 1;
select count(*) as aggregate from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` = '5';
select `posts`.*, `posts_categories`.`category_id` as `pivot_category_id`, `posts_categories`.`post_id` as `pivot_post_id` from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` = '5' limit 10 offset 0;
If you would like to improve it, you will probably need to not use Eloquent in this case and use join - but is it worth it? You would now need to manually paginate results without paginate() so it would probably won't be want you want to achieve.
EDIT
What you probably do is:
you get all posts that belongs to the category (but in fact you want to paginate only 10 of them)
for each post you want do display all categories it belongs to.
To lower number of queries you should use:
$category = Category::where('id','=',$id)->first();
$posts = $category->posts()->with('categories')->paginate(10);
and to display it you should use:
foreach ($posts as $p) {
echo $p->name.' '."<br />";
foreach ($p->categories as $c) {
echo $c->name."<br />";
}
}
It should lower your number queries to 4 from 130

Resources