How to average multiple columns using Eloquent? - laravel

I'm looking to get the average value across multiple columns on a related model, something like this:
$this->reviews()->avg('communication', 'friendliness')
Where communication and friendliness are an array of column names. However it appears the aggregate functions only support single column names, so I'm doing this:
$attributes = array('communication', 'friendliness');
$score = array();
foreach ($attributes as $attribute)
{
$score[] = $this->reviews()->avg($attribute);
}
return round(array_sum($score) / sizeof($attributes), 1);
Which results in multiple queries. Any suggestions for a best practice here?
Thanks

To avoid multiple queries you can use a raw database expression within Eloquent as shown below:
$averages = $this->reviews()
->select(DB::raw('avg(communication) c, avg(friendliness) f'))
->first();
echo $averages->c;
echo $averages->f;
Since the aggregate function name avg is recognized by all supported database by Laravel, this will not be a big deal.

Related

How to found item in sql Array laravel

I am trying to find an item by id, but my column is a comma separated string(primaryCategory in below screenshot). I want to get this field in my result when query by any string (i.e. by 50,20,41 etc).
I know there could be options like whereIn, whereHas etc. but not able to find correct syntax. Please help me with this.
Using comma separated string is bad practice for that task, better to create a new table.
But for your structure this way with func FIND_IN_SET is suitable:
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_find-in-set
$categories = [1,2,3];
$queryParts = [];
foreach($categories as $category) {
$queryParts[] = 'FIND_IN_SET("' . $category . '", `primaryCategory`)';
}
$query = implode(' OR ', $queryParts);
$list = Model::whereRaw($query)->get();
UPD
Righ solution
Use *ToMany relation:
https://laravel.com/docs/8.x/eloquent-relationships#one-to-many
https://laravel.com/docs/8.x/eloquent-relationships#many-to-many
You have to create a new table: model_categories and put there model_id and category_id. Every model can have several categories and vise versa, and then you can use the standard laravel whereHas function to find models with special categories.

How to generate a query to db if several “where” and “update” are added in the loop?

I have a method with four incoming arrays.
The first two arrays $whereColumns[] and $updateColumns[] contain columns names.
The third column $whereFilters[] contains filters for where methods.
And the last one $updateFilters[] contains values for updating.
How can I generate only one search query with several where methods and several updated columns? Now I know only how create a query with several where methods in a loop.
for($i=0; $<count($whereColumns); $i++){
$query->where($whereColumns[$i], '=', $whereFilters[$i]);
}
$result = $query->get();
But how to generate a query with update like this (but this doesn't work):
for($i=0; $i<count($updateColumns); $i++){
$query->update($updateColumns[$i] => $updateFilters[$i]);
}
$result = $query->get();
Both of these loops should generate one search query in total for updating a table data.
You can loop through your $updateColumns and create an array containing all columns and values like this:
$updateArray = [$column[0] => $value[0], $column[1] => $value[1]];
Then you can put this whole array into the query
$query->update($updateArray);

When to use codeigniter where() function and when get_where()

I've been reading docs regarding these 2 functions, but I still can't quite get the difference between these two.
I get it that get_where selects data from DB, but when should we use where() function and not get_where()?
get_where()
There are tons of other ways to get data using CodeIgniter’s ActiveRecord implementation, but you also have full SQL queries if you need them:
Example:
$query = $this->db->get_where('people', array('id' => 449587));
Ultimately, get_where() is the naive case, and certainly the most commonly-used in my code anyway — I can’t think of an another framework in any other language that enables you to be this productive with data with a single line of code.
get_where([$table = ''[, $where = NULL[, $limit = NULL[, $offset = NULL]]]])
Parameters:
$table (mixed) – The table(s) to fetch data from; string or array
$where (string) – The WHERE clause
$limit (int) – The LIMIT clause
$offset (int) – The OFFSET clause
This function is working as get() but with also allows the WHERE to be added directly.
Identical to the $this->db->get(); except that it permits you to add
a where clause in the second parameter, instead of using the
db->where() function.
where()
This function enables you to set WHERE clauses in your query.
You can also add where clauses, sort conditions and so forth:
$this->db->select('first_name', 'last_name');
$this->db->from('people');
$this->db->where('id', 449587);
$this->db->order_by('last_name, first_name');
$query = $this->db->get();
It’s possible to chain all these conditions together on a single line, but I prefer putting them on separate lines for readability.
In simple word, get_where is a luxury to use but where() gives you more flexibility to use.
The get_where is a combined function -so to speak- of the both where() and get() functions,
according to the documentation :
$this->db->get_where()
Identical to the above function except that it permits you to add a
"where" clause in the second parameter, instead of using the
db->where() function
also by take a quick look at the source code of the get_where() method you will notice that
if ($where !== NULL)
{
$this->where($where);
}
where $where is the second parameter of get_where() method.
In simple terms, $this->db->get_where('table name', 'where clause') is an alias for $this->db->where('where clause')->get('table name');

Laravel - Collection with relations take a lot of time

We are developing an API with LUMEN.
Today we had a confused problem with getting the collection of our "TimeLog"-model.
We just wanted to get all time logs with additional informationen from the board model and task model.
In one row of time log we had a board_id and a task_id. It is a 1:1 relation on both.
This was our first code for getting the whole data. This took a lot of time and sometimes we got a timeout:
BillingController.php
public function byYear() {
$timeLog = TimeLog::get();
$resp = array();
foreach($timeLog->toArray() as $key => $value) {
if(($timeLog[$key]->board_id && $timeLog[$key]->task_id) > 0 ) {
array_push($resp, array(
'board_title' => isset($timeLog[$key]->board->title) ? $timeLog[$key]->board->title : null,
'task_title' => isset($timeLog[$key]->task->title) ? $timeLog[$key]->task->title : null,
'id' => $timeLog[$key]->id
));
}
}
return response()->json($resp);
}
The TimeLog.php where the relation has been made.
public function board()
{
return $this->belongsTo('App\Board', 'board_id', 'id');
}
public function task()
{
return $this->belongsTo('App\Task', 'task_id', 'id');
}
Our new way is like this:
BillingController.php
public function byYear() {
$timeLog = TimeLog::
join('oc_boards', 'oc_boards.id', '=', 'oc_time_logs.board_id')
->join('oc_tasks', 'oc_tasks.id', '=', 'oc_time_logs.task_id')
->join('oc_users', 'oc_users.id', '=', 'oc_time_logs.user_id')
->select('oc_boards.title AS board_title', 'oc_tasks.title AS task_title','oc_time_logs.id','oc_time_logs.time_used_sec','oc_users.id AS user_id')
->getQuery()
->get();
return response()->json($timeLog);
}
We deleted the relation in TimeLog.php, cause we don't need it anymore. Now we have a load time about 1 sec, which is fine!
There are about 20k entries in the time log table.
My questions are:
Why is the first method out of range (what causes the timeout?)
What does getQuery(); exactly do?
If you need more information just ask me.
--First Question--
One of the issues you might be facing is having all those huge amount of data in memory, i.e:
$timeLog = TimeLog::get();
This is already enormous. Then when you are trying to convert the collection to array:
There is a loop through the collection.
Using the $timeLog->toArray() while initializing the loop based on my understanding is not efficient (I might not be entirely correct about this though)
Thousands of queries are made to retrieve the related models
So what I would propose are five methods (one which saves you from hundreds of query), and the last which is efficient in returning the result as customized:
Since you have many data, then chunk the result ref: Laravel chunk so you have this instead:
$timeLog = TimeLog::chunk(1000, function($logs){
foreach ($logs as $log) {
// Do the stuff here
}
});
Other way is using cursor (runs only one query where the conditions match) the internal operation of cursor as understood is using Generators.
foreach (TimeLog::where([['board_id','>',0],['task_id', '>', 0]])->cursor() as $timelog) {
//do the other stuffs here
}
This looks like the first but instead you have already narrowed your query down to what you need:
TimeLog::where([['board_id','>',0],['task_id', '>', 0]])->get()
Eager Loading would already present the relationship you need on the fly but might lead to more data in memory too. So possibly the chunk method would make things more easier to manage (even though you eagerload related models)
TimeLog::with(['board','task'], function ($query) {
$query->where([['board_id','>',0],['task_id', '>', 0]]);
}])->get();
You can simply use Transformer
With transformer, you can load related model, in elegant, clean and more controlled methods even if the size is huge, and one greater benefit is you can transform the result without having to worry about how to loop round it
You can simply refer to this answer in order to perform a simple use of it. However incase you don't need to transform your response then you can take other options.
Although this might not entirely solve the problem, but because the main issues you face is based on memory management, so the above methods should be useful.
--Second question--
Based on Laravel API here You could see that:
It simply returns the underlying query builder instance. To my observation, it is not needed based on your example.
UPDATE
For question 1, since it seems you want to simply return the result as response, truthfully, its more efficient to paginate this result. Laravel offers pagination The easiest of which is SimplePaginate which is good. The only thing is that it makes some few more queries on the database, but keeps a check on the last index; I guess it uses cursor as well but not sure. I guess finally this might be more ideal, having:
return TimeLog::paginate(1000);
I have faced a similar problem. The main issue here is that Elloquent is really slow doing massive task cause it fetch all the results at the same time so the short answer would be to fetch it row by row using PDO fetch.
Short example:
$db = DB::connection()->getPdo();
$query_sql = TimeLog::join('oc_boards', 'oc_boards.id', '=', 'oc_time_logs.board_id')
->join('oc_tasks', 'oc_tasks.id', '=', 'oc_time_logs.task_id')
->join('oc_users', 'oc_users.id', '=', 'oc_time_logs.user_id')
->select('oc_boards.title AS board_title', 'oc_tasks.title AS task_title','oc_time_logs.id','oc_time_logs.time_used_sec','oc_users.id AS user_id')
->toSql();
$query = $db->prepare($query->sql);
$query->execute();
$logs = array();
while ($log = $query->fetch()) {
$log_filled = new TimeLog();
//fill your model and push it into an array to parse it to json in future
array_push($logs,$log_filled);
}
return response()->json($logs);

MINUS operation in Eloquent ORM

Is there any equivalent MINUS operation from SQL using Eloquent ORM?
For example
$model1 = Model::where('some constraints applied')
$model2 = Model::where('some constraints applied')
I want to get all models that exist in $model1 but not in $model2
seblaze's answer looks good, though it will run 3 queries. Another option is diff() method of the Collection object:
$result = $model1->diff($model2);
This works after fetching data from the db with 2 queries, but complete set of data (unless there are more depending on your 'constraints applied').
The easiest way i see it is :
//Get the id's of first model as array
$ids1 = $model1->lists('id');
//get the id's of second models as array
$ids2 = $model2->lists('id');
//get the models
$models = Model::whereIn('id',$ids1)->whereNotIn('id',$ids2)->get();
This is not tested code, please read more about eloquent queries here

Resources