Laravel Eloquent subquery and count subquery results - laravel

Let's say there's a Post model. It has a hasMany relationship called comments() defined in its model.
I'm trying to:
Get the count of comments
That belongs to a specific set of Posts, defined by an array of ids
Where the comments are posted in a certain date range
I thought the following would work:
$ids = [1, 2, 3, 4];
$results = Post::whereIn('id', $ids)
->with(['comments'])
->whereHas('comments', function($q) {
$q->whereMonth('created_at', Carbon::now()->month);
})
->withCount('comments')
->get();
And then do
$count = array_sum($results->pluck('comments_count')->toArray());
The result of the query does include comments_count so that's working, and $count works as well.
But the date range in the subquery is not applied, it counts all comments. What am I missing?

You will need to pass the same condition/subquery to the withCount method:
->withCount(['comments' => function($q) {
$q->whereMonth('created_at', Carbon::now()->month);
}])
https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.html#method_withCount

Related

Querying other database using whererIn() and leftJoin() in the same query

To get the posts from another database corresponding to the id of the $ids array I use, for example, the following query (it works fine):
$ids = [1, 2, 4, 7];
$data = DB::connection('mysqlDatabase2')
->table('posts')
->whereIn('id_post', $ids)
->get();
Post has many comments.
The problem to solve is to also get the comments related to each post.
I tried with the following query, which returns two problems:
1- posts are returned duplicated;
2- return just one comment per post instead of an array with all related comments
$ids = [1, 2, 4, 7];
$data = DB::connection('mysqlDatabase2')
->table('posts')
->whereIn('posts.id_post', $ids)
->leftJoin('comments', 'post.id_post', '=', 'comments.id_post')
->get();
Edited:
The following is happening: if a post has three related comments, it returns the same post three times, each with one of the comments.
Dont Use Joins. You can use the Eloquent ORM
In Your Model Post.php file:
//If you want All comments related to posts
public function comments(){
return $this->hasMany(Comment::class, 'id_post')->orderBy('created_at', 'desc');
}
//If you want Single latest comment related to posts
public function latestComment(){
return $this->hasOne(Comment::class, 'id_post')->latest('created_at');
}
Now, in your controller you can use this like:
$data = Post::with('latestComment')->get();
Or
$data = Post::with('comments')->get();

check count in one to many relation in laravel

I have products table that each of the product has many orders,product.php
public function orders()
{
return $this->hasMany(Order::class,'product_id');
}
I can get products that order by order_count with this code:
$products = Product::withCount('orders')->orderBy('orders_count', 'desc')->get();
Now I want to get products in controller that their orders count is bigger than 5,
How I can do it?
One way to do this would to use whereHas():
$products = Product::withCount('orders')
->whereHas('orders', null, '>', 5)
->orderBy('orders_count', 'desc')
->get();
I don't think it's mentioned in the docs but you don't have to pass a closure as the 2nd param, and the 3rd and 4th can be used to pass the operator and the count.
Alternatively, you could use having() instead:
$products = Product::withCount('orders')
->having('orders_count', '>', 5)
->orderBy('orders_count', 'desc')
->get();
You can use mapping on your collection to filter out records e.g. :`
$collection = collect($products)->map(function ($product) {
return $product->orders->count() > 5;
})
->reject(function ($name) {
return $product->orders->count() < 5;
});`
This is just an example, you can put more conditions if required.
Hope this helps.
The below code should give you just the orders with a count greater than 5
$products = Product::withCount('orders')->where('orders_count', '>', 5)->orderBy('orders_count', 'desc')->get();
Adding ->where() should do it.
Hope that helps.
Update
Try
->whereRaw('count("orders.id") > 5')
This should work to get products with more than 5 orders.

Laravel eloquent: group the related models (e.g. all the comments for collection of posts)

To get the comments for a single Post: Post::find(1)->comments()
To get multiple Posts: Post::whereBetween('id',[1,10])
How to get all the combined comments for multiple Posts?
A la Post::whereBetween('id',[1,10])->comments()
Use pluck():
$posts = Post::whereBetween('id', [1, 10])->with('comments')->get();
$comments = $posts->pluck('comments')->flatten();
You can use this
$posts = Post::whereBetween('id',[1,10])->with('comments')->get();
Or if you wont to get only comments
$comments = App\Comment::whereHas('post', function ($query) {
$query->whereBetween('id', [1, 10]);
})->get();
Or if related primary key post_id
$comments = App\Comment::whereBetween('post_id', [1, 10])->get();
If you just want the comments, and assuming you have a post_id field in your Comment model, and you have the ids of the posts as you've indicated:
$comments = Comment::whereIn('post_id', [1,2,3,4,5,6,7,8,9,10])->get();

Order by sum column relationship in Laravel

I have this controller which grabs posts from a post table.
Every post in the posts table have the relation "hasMany" with another table likes.
Controller:
public function getDashboard(){
$posts = Post::orderBy('created_at', 'desc')->get();
return view('dashboard', ['posts' => $posts]);
}
I'd like to replace 'created at' with something like:
$post->likes->sum(like)
Don't know how to write the right syntax though.
EDIT:
Here are the tables.
Posts
--id
--body
Likes
--id
--post_id
--like
The column like can have the value 1 or -1.
I'd like to order on the summation of that column for each post.
So a post with one dislike(-1) and one like(1) will have the aggregated value of 0, hence will be placed after a post with one like(1).
You can use withCount() for this as:
Post::withCount('likes')->orderBy('likes_count')->get()
withCount() will place a {relation}_count column on your resulting
models
Update
Post::withCount(['likes' => function($q) {
$q->where('like', 1)
}])
->orderBy('likes_count')
->get()
Update2
You can use sortByDesc() to sort your collection as:
$posts = Post::get();
$posts = $posts->sortByDesc(function ($post) {
return $post->likes->sum('like');
});
If you want to sum column value from relationship and then sort records.
$users = User::addSelect(['likes' => Post::selectRaw('sum(likes) as total_likes')
->whereColumn('user_id', 'useres.id')
->groupBy('user_id')
])
->orderBy('likes', 'DESC')
->get()
->toArray();
Or
use below
$users = User::select("*",
\DB::raw('(SELECT SUM(likes) FROM likes_table WHERE likes_table.user_id = users.id) as tolal_likes'))
->orderBy('likes', 'DESC')
->get()
->toArray();

Laravel limit relation data

I have the following eloquent query:
$move['topUsers'] = UserMoves::where('move_id',$move->id)
->with(array('userBase'=>function($query){
$query->select('id','name')->orderBy('power','desc')->take(3);
}))->get();
The query works fine but the ->take(3) doesn't. I get all of the relation results while I'm looking only for the first 3, ordered by power column.
the ->orderBy('power','desc')->take(3) is not working in the $query.
How I can limit the realtion data to 3 results ordered by the power column?
Try method from this article which is exactly about limiting relationship while eager loading:
// New relationship
public function mostPowerful()
{
return $this->hasOne('userBase')->orderBy('power', 'desc');
}
// Get data
$move['topUsers'] = UserMoves::where('move_id',$move->id)
->with('mostPowerful')
->take(3)
->get();
Try this.
$move['topUsers'] = DB::table('your_table_name')
->where('move_id', $move->id)
->select('id','name','power')
->orderBy('power', 'desc')
->take(3)
->get();
Hope this work.
Without array:
$topusers = UserMoves::where('move_id',$move->id)
->with(['userBase'=>function($query){
$query->select('id','name')->orderBy('power','desc')->take(3);
}])->get();

Resources