need help optimizing laravel complex query - mysql

Laravel code:
$posts = array();
$allPosts = DB::table('post_categories')
->Join('posts', 'posts.id', '=', 'post_categories.posts_id')
->select('posts.title','posts.id','posts.body','posts.created_at')
->where('post_categories.categories_id','!=',5)
->orderBy('posts.created_at','desc')
->get();
foreach ($allPosts as $post){
$categories = DB::table('post_categories')
->Join('categories', 'categories.id', '=', 'post_categories.categories_id')
->select('categories.name','categories.id')
->where('post_categories.posts_id','=',$post->id)
->get();
$post->categories = $categories;
array_push($posts,$post);
}
Model relations:
Posts 1 - m post_categories m - 1 categories
first query is used fetch posts without category number 5
second is used to fetch categories in a post.
problem is i have a n+1 query due to the for loop.
so i was wondering if there was a better way to do this without the for loop
debugbar result:
screenshot from debugbar

post_categories looks like a many:many mapping table. If so follow the indexing advice in http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table
Also, posts needs INDEX(created_at).
For further discussion, please provide SHOW CREATE TABLE and EXPLAIN SELECT ...

Related

Laravel GroupBy and Count using eloquent model

I have a table
'new_comments'
with fields
id,user_id,
title,
comment_description
I have another table named
'comments_upvote'
having
field user_id,
post_id,
likes
id of new_comments and post_id of comments_upvote table are same. we have to take those comments which have the most likes. how we fetch that data.
$ud = Comments_upvote::select('post_id')->groupby('post_id')-
>orderby(DB::raw('count(likes)'), 'desc')->get();
$postid = array();
foreach ($ud as $key => $value) {
$postid[] = $value->post_id;
}
$data = DB::table('new_comments')->whereIn('id',$postid)->get();
but the problem is that i have to count all likes whose value = 1 how can we do that.
If you showed us some code you'd get a more concrete answer, but here's a quick outline of what you need: define your relationships (make sure you use your custom foreign key), use whereHas, group by post_id count, and sort by count descending.
So I'm guessing you have at least two models NewComment and CommentUpvote (you should). In the NewComment define a 1:n relationship:
public function upvotes(){
$this->hasMany('App\CommentUpvote', 'post_id');
}
Then in your controller (again, guessing, since you didn't show any code):
$bestComments = NewComment::whereHas('upvotes', function($query){
$query->select('post_id', DB::raw('count(*) as total'))->groupBy('post_id')->orderBy('total', 'desc');
})->get();
Disclaimer: this is untested and off the top of my head, but should nugde you in the right direction.

Laravel DB query builder left join with related counts

I am new in Laravel and facing an issue with the DB query builder.
I want to fetch all the records from users table and want to count related wishlists in wishlists table. Here is my query.
select `users`.*, count(wishlists.id) as wishlists_count from `users` left join `wishlists` on `users`.`id` = `wishlists`.`uid` where `users`.`status` = 1 group by `wishlists`.`id`
I want to do it with laravel query builder. Here is what I am trying.
$leads = DB::table('users')
->selectRaw('users.*', 'count(wishlists.id) as wishlists_count')
->leftJoin('wishlists', 'users.id', '=', 'wishlists.uid')
->where('users.status',1)
->groupBy('wishlists.id')
->get();
It is showing this error.
Argument 2 passed to Illuminate\Database\Query\Builder::selectRaw()
must be of the type array, string given.
Any help will be greatly appreciated.
Thanks in Advance.
This should work. From the documentation, the second argument to selectRaw() is an optional array of bindings.
However, you can achieve what you are looking for with a combination of select() and DB::raw()
$leads = DB::table('users')
->select('users.*', DB::raw('count(wishlists.id) as wishlists_count'))
->leftJoin('wishlists', 'users.id', '=', 'wishlists.uid')
->where('users.status',1)
->groupBy('wishlists.uid')
->get();
Edit: The above answer is wrong. I would consider the following alternatives instead.
Either you could achieve it with a Sub-Query with something like the following:
$wishlist = Wishlist::select(DB::raw('count(wishlists.id)'))
->whereColumn('uid', 'users.id')
->getQuery();
$users = User::select('users.*')
->selectSub($wishlist, 'wishlists_count')
->get();
Or with a much easier way (assuming you have a wishlists relationship set up)
Users::withCount('wishlists')->get();
foreach($users as $user) {
echo $user->wishlists_count;
}

Join table one to many relationship only 1 row from second table

I have Items and Images tables with one to many relationship.
1 item can have many images, but i need only 1 image to join the table.
$items = DB::table('items')
->leftjoin('item_images', function($join){
$join->on('item_images.item_id','=','items.id');
})
->select('items.id', 'items.name', 'item_images.image_file')
->get()
this will return all images, but I want only first image.
first() is not working, limit(1) is not working
I want the result : 1 item with its first image. how to achive this?
Try this
$items = DB::table('items')
->leftjoin('item_images','items.id','=','item_images.item_id')
->select('items.id', 'items.name', 'item_images.image_file')
->distinct()
->get();
You can use join, but its better to us eloquent relationship.
You have to tackle this problem in three steps:
Step 1: Find the id's of first image for each item
$minQuery = DB::table('item_images')
->select(DB::raw('min(id) AS id'))
->groupBy('item_id')
->toSql();
Step 2: Retrieve details of those images
$joinQuery = DB::table('item_images')
->select('item_images.id', 'item_images.item_id', 'item_images.image_file')
->join(DB::raw("($minQuery) AS m"), 'item_images.id', '=', 'm.id')
->toSql();
Step 3: Join it with items table
$items = DB::table('items')
->select('items.id', 'items.name', 'ii.image_file')
->leftJoin(DB::raw("($joinQuery) AS ii"), 'items.id', '=', 'ii.item_id')
->get();
I haven't tested this on a machine

Laravel. Using WHERENOTIN

Basically I want to build tihs query in Laravel, but it does not work.
SELECT films.id, films.name AS film
FROM films
WHERE films.id NOT IN
(
SELECT films.id
FROM actors, actor_film, films
WHERE actors.id = actor_film.actor_id
AND actor_film.film_id = films.id
GROUP BY films.id
)
ORDER BY films.id DESC
LIMIT 600
;
Using a "whereNotIn" I have written these two queries:
The first one get all films in the Data Base that has at least an actor associated like this:
$films_with_actors = DB::table('films')
->join('actor_film', 'actor_film.film_id', '=', 'films.id')
->join('actors', 'actors.id', '=', 'actor_film.actor_id')
->select( 'films.id')
->groupBy('films.id')
->get();
Now I want to get the films that do not have associated an actor. For that I am trying to get the ID that are not included in the previous method, like this:
$films_with_no_actors = DB::table('films')
->whereNotIn('films.id', $films_with_actors)
->orderBy('films.id', 'desc')
->take(500)
->get();
-
Any help?
I am giving you a basic solution based on the code you shared.
In laravel you have a method called pluck for retrieving an array with all the values for a given key.
Therefore, you can get only the ids for the $films_with_actors. Something like (based on your first query):
$films_with_actors = DB::table('films')
->join('actor_film', 'actor_film.film_id', '=', 'films.id')
->join('actors', 'actors.id', '=', 'actor_film.actor_id')
->select( 'films.id')
->groupBy('films.id')
->pluck('id')->toArray();
Now you have an array with the ids and you can include that array in the whereNotIn clause of your second query:
$films_with_no_actors = DB::table('films')
->whereNotIn('films.id', $films_with_actors)
->orderBy('films.id', 'desc')
->take(500)
->get();

Laravel query help needed

I have a question about Laravel queries, so I have a upload Model and database table which stores all my images. Then there is an Activity Model and database table which stores all my activities. For each activity I want a image.
So my Activity model has a 'uploads_id' column. I wrote the query like this:
$activity_images = DB::table('uploads')
->join('activities', 'uploads.id', '=', 'activities.upload_id')
->where('uploads.id', '=', 'activities.upload_id')
->get();
It cannot find the right image what am I doing wrong?
You are "joining" twice in your join and where clause. You connect uploads.id and activities.upload_id one time in the join and again in the where clause.
If you want to query for a special upload.id, your query should look like this:
$activity_images = DB::table('uploads')
->join('activities', 'uploads.id', '=', 'activities.upload_id')
->where('uploads.id', '=', '<yourUploadID>')
->get();
If you want all images, you can delete the where statement.
$activity_images = DB::table('uploads')
->join('activities', 'uploads.id', '=', 'activities.upload_id')
->get();
If you use join an inner join will be used.
You could also use leftJoin if you want all entries from the table uploads.
$activity_images = DB::table('uploads')
->leftJoin('activities', 'uploads.id', '=', 'activities.upload_id')
->get();
Does it solve your issue?