Laravel slow performance on pagination with large data - mysql

I have 100k record in my posts table and 100k records in users table.
When i use laravel's pagination. it takes too long to return data...!
Code:
$posts = Post::join('users', 'posts.user_id', 'users.id')
->where('posts.status', '=', 1)
->where('users.status', '=', 1)
->paginate(10);
How can i fix this and got better performance?
Note
I have this problem also when i use inRandomOrder() on $posts

You can use Laravel simplePaginate() method.
It will show only "Next" and "Previous" links, if that is enough for you.
https://laravel.com/docs/5.0/pagination#usage

Related

Laravel query using with() method with unproper sub-query

I perform the following query with no problem. But it doesn't get the results I want.
I want 3 comments for each post, but I think it gets 3 comments totally. How can I resolve this?
$posts = Post::with([
'comments' => function($c) {
$c->orderBy('commentTimestamp', 'desc')->take(3)->get();
}
])
->take(10)
->get();
Your query is totally valid, I just ran it myself and got the right results, please check your database, if you do SELECT * FROM comments do you get records?

Very slow query when using Eloquent whereNotIn

I have a Question model from very large table of questions (600,000 records), with relation to Customer,Answer and Product models. Relations are irrelevant to this question but I mentioned them to clarify I need to use Eloquent. When I call Question::with('customer')->get(); it runs smoothly and fast.
But there is another table in which I have question_ids of all questions which should not be shown (for specific reasons).
I tried this code:
// omitted product ids, about 95,000 records
$question_ids_in_product = DB::table('question_to_product')
->pluck('product_id')->all();
$questions = Question::with('customer')
->whereNotIn('product_id', $question_ids_in_product)
->paginate($perPage)->get();
It takes so much time and shows this error:
SQLSTATE[HY000]: General error: 1390 Prepared statement contains too many placeholders
and sometimes Fatal error: Maximum execution time of 30 seconds exceeded
When I run it with plain sql query:
SELECT * FROM questions LEFT JOIN customers USING (customer_id)
WHERE question_id NOT IN (SELECT question_id FROM question_to_product)
it takes only 80 milliseconds
How can I use Eloquent in this situation?
You can use whereRaw method:
$questions = Question::with('customer')
->whereRaw('question_id NOT IN (SELECT question_id FROM question_to_product)')
->paginate($perPage)->get();
But ideally as you found out this is a better sollution:
Question::with('customer')->whereNotIn('question_id',
function ($query) {
$query->from('question_to_product') ->select('question_id');
}
);
Difference?
When you will migrate your database to another database the whereRaw might not work as you put in raw statements.
That is why we have Eloquent ORM which handles these transitions and build the appropriate queries to run.
No performance impact because the SQL is the same (for MySQL)
P.S: For better debugging try installing this debug bar
refer from https://laravel.com/docs/5.4/queries#where-clauses
$users = DB::table('questions')
->leftJoin('customers', 'curtomer.id', '=', 'question.user_id')
->whereNotIn('question_id', [1, 2, 3])
->get();
It'll work 100%. When you query getting longer to response like more than 30 seconds when you are using whereNotIn. Use this Query Syntax.
$order = Order::on($databaseCredentials['database'])
->whereRaw('orders_id NOT IN (SELECT orders_id FROM orders)')
->skip($page)
->take(10)
->orderBy('orders.updated_at', 'ASC')
->paginate(10);

MySQL to Eloquent query

I've been having some issues with a query. I'm trying to get the most recent record for each 'category' from a table, but I can't figure out how to write the query in Laravel.
I got the query working in MySQL, but no real luck in translating it.
The MySQL query looks like this:
SELECT *
FROM messages
WHERE id IN (
SELECT MAX(id)
FROM messages
GROUP BY conversation_id
);
I was trying something like this in Laravel, but it doesn't seem to work:
return self::where(function($query){
$query->select(max(['id']))
->from('messages')
->groupBy('conversation_id');
})
->get();
(Posted on behalf of the OP).
Thanks to AlexM's comments I figured it out.
return self::whereIn('id', function($query){
$query->select(max(['id']))
->from('messages')
->orderBy('created_at', 'desc')
->groupBy('conversation_id');
})
->get();
Was my first solution but that didn't work quite well. It was selecting two records as intended, but not the last ones.
I've then come up with the idea to use selectRaw instead select, which solved my issue perfectly. The final query looks like this, for any interested:
return self::whereIn('id', function($query){
$query->selectRaw('max(id)')
->from('messages')
->orderBy('created_at', 'desc')
->groupBy('conversation_id');
})
->get();

Laravel 5.3 Query - Left join some table

I'm trying to get the most recent record for each candidate_id from a ìnterviews` table.
This is what I want to achive:
I'm using Eloquent on laravel and have already tried this methods (with and without eloquent):
$candidates = DB::table('interviews')->select('interviews.*', 'i2.*')
->leftJoin('interviews as i2',
function ($join) {
$join->on('interviews.candidate_id', '=', 'i2.candidate_id');
$join->on('interviews.created_at', '<', 'i2.created_at');
}
)
->whereNull('i2.candidate_id')
->get();
and with eloquent I've tried this:
$candidates = Interview::leftJoin('interviews as i2',
function ($join) {
$join->on('interviews.candidate_id', '=', 'i2.candidate_id');
$join->on('interviews.created_at', '<', 'i2.created_at');
}
)->whereNull('i2.candidate_id')
->get();
If I change get() to toSql() I have exactly the same query that's shown on the above image, but running on laravel I'm getting always these results (this using the first method, with query builder):
Anyone know why I get this results? Is hard to understand that laravel is doing the same query that I do in HeidiSql but I get diferent results :(
Any tip?
Thanks in advance!
Because you are using ->select('interviews.*', 'i2.*') combined with ->whereNull('i2.candidate_id') I am assuming the second select parameter is overriding all fields on the interviews table with nulls, try reversing the order to ->select('i2.*','interviews.*') or not use the i2.* at all.
This is because the output ignores the alias and only uses the fieldname as element key in the returned collection.
Hope it works.
Perfect case scenario you pick the exact columns you want from each of the joined tables for e.g. it may go like this: table1.id,table1.column1,table1.column2,table2.column2 as smth_so_it_doesnt_override

How to format Laravel Eloquent query with optional sections

Im new to Laravel, but am struggling with how Eloquent queries can have optional sections.
I have the following Eloquent query at the moment:
Posts::where('approved', '=', 'Y')->orderBy('created_at', 'DESC')->take($noofposts)->skip($skipno)->get();
That works fine. However I now need to add two optional sections, which i'd like to do without having to duplicate the query each time.
I need to add AND WHERE userid=$x (looped for one or more times) if $x (which is an array) is present, if its not it should ignore then ... and finally add AND (WHERE status=$y[0] OR $status=$y[1] OR $status=$y[2]) - again if the status flags are not set, then just ignore.
Basically if no flags are set I end up with the original query, but if they are we get
Posts::where('approved', '=', 'Y')->where('userid', '=', '2')->where('userid', '=', '23')->where('status', '=', 'K')->orWhere('status', '=', 'N')->orderBy('created_at', 'DESC')->take($noofposts)->skip($skipno)->get();
I can work it out perfectly in normal PHP, but cannot understand how it would work in Laravel Eloquent.
Can anyone point me in the right direction? Neither the user guide nor any website examples seem to look at this kind of scenario!
Fist, instead of chaining orWhere I would use the whereIn function of Eloquent.
whereIn('status', $y);
The problem is, if $y is empty, the request won't work. (I think it just crashes)
So if you want to avoid controls and keep your code clean you can add a query scope in you Post model.
http://laravel.com/docs/4.2/eloquent#query-scopes
scopeOptionalWhereIn($query, $field, $array){
if(!empty($array))
return $query->whereIn($field, $array);
return $query; //return unchanged query
}
Then you can use this scope in your query:
Posts::where('approved', '=', 'Y')
->optionalWhereIn('status', $y)
->orderBy('created_at', 'DESC')
->take($noofposts)
->skip($skipno)->get();
You can probably use the same scope to deal with the userid conditions.