Laravel collection count() takes too long - php

I have a contacts table and a contact_list table, a contact_list has many contacts, contacts table has a is_active field.
to get the number of active contacts for a contact_list i use the query builder as follows:
$contact_list->contacts()->where('is_active', 1)->count();
if the number of contacts is too big the query takes too long.
is there a better way to implement this?

as I can see you want to optimize the performance of your request.
to achieve that you should use eager-loading, so your new request should be like below:
$contactList = App\ContactList::with(['contacts' => function ($query) {
$query->where('is_active', 1);
}])->count();
For more information about how Laravel implement eager-loading please check the link of documentation: eager-loading documentation

With InnoDB COUNT() works slowly for tables with million rows.
That's a problem with count() as the InnoDB engine locks the table per row, and if the table is being changed on a regular bases the count() function is not trivial.
The Eloquent ORM doesn't differenciatie between InnoDB or MyISAM so the count() process always triggers something like SELECT COUNT(*) AS aggregate FROM table.
If you change the query with this one SELECT COUNT(1) FROM table you will realize that the count() process is way faster.
So in Laravel that would be reduced to something like:
Model::select(DB:raw('count(1)'))->first();
Or in your case
$contact_list->contacts()->select()->where('is_active', 1)->count();

Related

Select rows with equal values

How to do in Laravel?
1) get from table rows where user_id has the equal values?
2) return sum of some_amount values from this selected rows
Table:
- id;
- user_id;
- some_amount;
Table has bunch of certificates with some amount of money that belongs to different users. I need to find all certificates that belongs to one user (one user can have few certificates) and count how much money he have from all his certificates
Given you aren't looking for a solution to query this for an individual user, it sounds like you want to group by the user and sum the result of certificates.
The answer from #PhilCross is pretty close, you'd just need to modify it to add the group clause and remove the where condition. Something like this:
ModelName::groupBy('user_id')->sum('some_amount');
or
\DB::table('table_name')->groupBy('user_id')->sum('some_amount');
Generally eloquent or the query builder will have a method that relates to how you would do this in raw SQL.
I find it helpful to write out or think about how I would write the raw SQL and then slowly fill the eloquent or query builder in from that.
If you use a Model:
ModelName::where('user_id', 1)->sum('some_amount')
If you're using the query builder:
\DB::table('table_name')->where('user_id', 1)->sum('some_amount');
This is the documentation for the query builder:
https://laravel.com/docs/5.6/queries
Models: https://laravel.com/docs/5.6/eloquent
Model Relationships: https://laravel.com/docs/5.6/eloquent-relationships
Collections: https://laravel.com/docs/5.6/collections

Laravel pagination with distinct

I'm trying to use distinct with pagination but my pagination seems to ignore the total records of my distinct and it makes my pagination all messed up.
I have the following query:
$log_data_list = DB::table('logs')->leftjoin('users','logs.user_id', 'users.id')
->select(DB::raw("distinct on (logs.action,logs.action_table)logs.user_id,users.username as username,logs.created_at as tanggal,logs.ip_client as ip_client,logs.action as tindakan,logs.action_table as tabel,logs.no_laka as no_laka,logs.change_id as id"))
->paginate(5);
Can anyone help me with a solution? Thanks in advance
Instead of using a distinct in this fashion, why not have the log actions stored uniquely in a relational table using methods firstOrCreate / firstOrNew on storing the actual log.
You then would be able to use a proper eloquent model to paginate log actions table that would already be a unique list and use the ID of selection action to get all related logs.
table.logs id,action_id,users_id,...
table.logs_actions id,action_name
This would be less costly than a select distinct especially on a huge table.

Complex query join with doctrine

I'm building a query to show items with user and then show highest Bid on the item.
Example:
Xbox 360 by james. - the highest bid was $55.
art table by mario. - the highest bid was $25.
Query
SELECT i, u
FROM AppBundle:Item i
LEFT JOIN i.user u
I have another table bids (one to many relationship). I'm not sure how can I include single highest bid of the item in the same query with join.
I know I can just run another query after this query, with function (relationship), but I'm avoiding to do that for optimisation reasons.
Solution
SQL
https://stackoverflow.com/a/16538294/75799 - But how is this possible in doctrine DQL?
You can use IN with a sub query in such cases.
I am not sure if I understood your model correctly, but I attempted to make your query with a QueryBuilder and I am sure you will manage to make it work with this example:
$qb = $this->_em->createQueryBuilder();
$sub = $qb;
$sub->select('mbi') // max bid item
->where('i.id = mbi.id')
->leftJoin('mbi.bids', 'b'))
->andWhere($qb->expr()->max('b.value'))
->getQuery();
$qb = $qb->select('i', 'u')
->where($qb->expr()->in('i', $sub->getDQL()))
->leftJoin('i.user', 'u');
$query = $qb->getQuery();
return $query->getResult();
Your SQL query may look something like
select i,u
from i
inner join bids u on i.id = u.item_id
WHERE
i.value = (select max(value) from bids where item_id = i.id)
group by i
DQL, I don't think supports subqueries, so you could try using a Having clause or see if Doctrine\ORM\Query\Expr offers anything.
To solve this for my own case, I added a method to the origin entity (item) to find the max entity in a list of entities (bids), using Doctrine's Collections' Criteria I've written about it here.
Your Item entity would contain
public function getMaxBid()
{
$criteria = Criteria::create();
$criteria->orderBy(['bid.value' => Criteria::ASC]);
$criteria->setLimit(1);
return $this->bids->matching($criteria);
}
Unfortunately, there's no way that i know to find the maximum bid and the bidder with one grouping query, but there's several techniques to making the logic work with several queries. You could do a sub select and that might work fine depending on the size of the table. If you're planning on growing to the point where that's not going to work, you're probably already looking at sharing your relational databases, moving some data to a less transactional, higher performance db technology, or denormalizing, but if you want to keep this implemented in pure MySQL, you could use a procedure to express in multiple commands how to check for a bid and optionally add to the list, also updating the current high bidder in a denormalized high bids table. This keeps the complex logic of how to verify the bid in one, the most rigorously managed place - the database. Just make sure you use transactions properly to stop 2 bids from being recorded concurrently ( eg, SELECT FOR UPDATE).
I used to ask prospective programmers to write this query to see how experienced with MySQL they were, many thought just a max grouping was sufficient, and a few left the interview still convinced that it would work fine and i was wrong. So good question!

Yii relation generates GROUP BY clause in the query

I have User, Play and UserPlay model. Here is the relation defined in User model to calculate total time, the user has played game.
'playedhours'=>array(self::STAT, 'Play', 'UserPlay(user_id,play_id)',
'select'=>'SUM(duration)'),
Now i am trying to find duration sum with user id.
$playedHours = User::model()->findByPk($model->user_id)->playedhours)/3600;
This relation is taking much time to execute on large amount of data. Then is looked into the query generated by the relation.
SELECT SUM(duration) AS `s`, `UserPlay`.`user_id` AS `c0` FROM `Play` `t` INNER JOIN
`UserPlay` ON (`t`.`id`=`UserPlay`.`play_id`) GROUP BY `UserPlay`.`user_id` HAVING
(`UserPlay`.`user_id`=9);
GROUP BY on UserPlay.user_id is taking much time. As i don't need Group by clause here.
My question is, how to avoid GROUP BY clause from the above relation.
STAT relations are by definition aggregation queries, See Statistical Query.
You cannot remove GROUP BY here and make a meaningful query for aggregate data. SUM(), AVG(), etc are all aggregate functions see GROUP BY Functions, for a list of all aggregate functions supported by MYSQL.
Your problem is for the calculation you are doing a HAVING clause. This is not required as HAVING checks conditions after the aggregation takes place, which you can use to put conditions like for example SUM(duration) > 500 .
Basically what is happening is that you are grouping all the users separately first, then filtering for the user id you want. If you instead use a WHERE clause which will filter before not after then aggregation is for only the user you want then group it your query will be much faster.
Although Active Record is good at modelling data in an OOP fashion, it
actually degrades performance due to the fact that it needs to create
one or several objects to represent each row of query result. For data
intensive applications, using DAO or database APIs at lower level
could be a better choice
Therefore it is best if you change the relation to a model function querying the Db directly using the CommandBuilder or DAO API. Something like this
Class User extends CActiveRecord {
....
public function getPlayedhours(){
if(!isset($this->id)) // to prevent query running on a newly created object without a row loaded to it
return 0;
$played = Yii::app()->db->createCommand()
->select('SUM(duration)')
->from('play')
->join("user_play up","up.play_id = play.id")
->where("up.user_id =".$this->id)
->group("up.user_id")
->queryScalar();
if($played == null)
return 0;
else
return $played/3600 ;
}
....
}
If you query still is slow, try optimizing the indexes, implement cache mechanism, and use the explain command to figure out what is actually taking more time and more importantly why. If nothing is good enough, upgrade your hardware.

How to return multiple results from an array of options?

I have an array of data :
$ary = new array(
array("domain"=>"domain1", "username"=>"username1"),
array("domain"=>"domain1", "username"=>"username2"),
array("domain"=>"domain2", "username"=>"username3"),
);
I need to use this data to retrieve data from a MySql database with the following table structure (simplified for illustration).
domain_table
domain_id
domain
user_table
user_id
user
stuff_table
stuff_id
... details
link_table
link_id
user_id -- The user we are searching for connections on
connected_user_id -- from the array above
stuff_id
I need to fetch every row in the stuff table for a single user_id that also has a connected_user_id from the array.
I'm using PDO
There may be hundreds (possibly thousands) of entries in $ary.
I could generate a very large query by looping thorugh the array and adding loads of joins.
I could perform a single query for each row in $ary.
I could create a temporary table with $ary and use a join.
Something else?
What is the best way - fastest processor time without being too arcane - to achieve this?
perform a single query for each row - bed way because of small speed
many joins better then 1.
if it is possible - make view & use it.
If your entire dataset is HUGE and doesn't fit in memory, joins shouldn't be your choice.
Do sequential selects. Select rows from your link_table, gather user_id's out of the result in PHP. Then select rows from user_table using "where user_id in (?)". Handle grouping of results in PHP.
Even with large tables selects by key will be fast. And having 2-5 selects instead of 1 select is not a problem.
Joins will be fast while your DB fits into RAM. Then problems arise.

Categories