Performing a sum() on a collection in Laravel - php

I have an application with a basic forum system where users can "like" a topic multiple times. My models extend Eloquent and I'm trying to get the sum of votes a user has for a specific topic... Basically, I'm trying to accomplish something like:
$votes = Auth::user()
->votes->has('topic_id', '=', $topic->id)
->sum('votes');
However, when executing this, I get the following error...
Call to a member function sum() on a non-object
I've also tried
public function show($forumSlug, $topicSlug)
{
$topic = Topic::whereSlug($topicSlug)->first();
$votes = Topic::whereHas('votes', function ($q) use ($topic)
{
$q->where('topic_id', '=', $topic->id)->sum('votes');
});
dd($votes);
}
However, with that I receive an error stating:
Unknown column 'ideas.id' in 'where clause' (SQL: select sum(votes)
as aggregate from votes where votes.idea_id = ideas.id and
idea_id = 1)`

You may try something like this (Not sure about your relationship but give it a try):
$topic = User::with(array('topics' => function ($query) use ($topic_id) {
// $query = Topic, so it's: Topic::with('votes')
$query->with('votes')->where('topics.id', $topic_id);
}))->find(Auth::user()->id)->topics->first();
// Count of total votes
dd($topic->votes->count());
P/S: If it doesn't work then please post your model's relationship methods.

I managed to get it working, though I'm not sure I like this approach. I'd love to hear if anyone knows of a better way of doing this...
Basically, I used my relationships to filter() the votes and then used sum() on the filtered collection.
public function show($forumSlug, $topicSlug)
{
$userId = is_null(Auth::user()) ? false : Auth::user()->id;
$topic = Topic::whereSlug($topicSlug)->first();
$votes = $topic->votes->filter(function ($votes) use ($userId)
{
return $votes->user_id == $userId;
})->sum('votes');
return View::make('forums.topics.show', compact('topic', 'votes'));
}

Related

data repeating when two tables are joined in codeigniter

I tried to fetch data using joins and the data is repeating,
The controller code is:
public function searchjobs2()
{
//$id=$_SESSION['id'];
$lan = $_POST["picke"]; //var_dump($id);die();
$value['list']=$this->Free_model->get_jobs($lan);//var_dump($value);die();
$this->load->view('free/header');
$this->load->view('free/searchjobs2',$value);
}
And the model:
public function get_jobs($lan)
{
$this->db->select('*');
$this->db->from("tbl_work_stats");
$this->db->join("tbl_work", "tbl_work.login_id = tbl_work_stats.login_id",'inner');
$this->db->where("language LIKE '%$lan%'");
// $this->db->where('tbl_work_stats.login_id',$id);
$this->db->order_by('insertdate','asc');
$query=$this->db->get()->result_array();//var_dump($query);die();
return $query;
}
I have used
foreach ($list as $row){
...
}
for listing.
Using distinct will remove duplicate fields:
$this->db->distinct();
From what I can see, your query has ambiguity, and an error in the join statement, also your where like is part of the problem, I would recommend trying this even do there are some missing info, find out wich field you need to join from the second table.
public function get_jobs($lan){
$this->db->select("tbl_work_stats.*, tbl_work.fields");
$this->db->from("tbl_work_stats");
$this->db->join("tbl_work", "tbl_work_stats.login_id = tbl_work.login_id","inner");
$this->db->where("tbl_work.language LIKE", "%" . $lan . "%" );
$this->db->order_by("tbl_work_stats.insertdate","asc");
$query=$this->db->get()->result_array();
return $query;}
do you mean to join on login_id?
I am guessing that is the user logging in and it is the same for many entries of tbl_work_stats and tbl_work.
you didn't post your schema, , but login_id doesn't seem like right thing to join on. how about something like tbl_work.id = tbl_work_stats.tbl_work_id or similar?
also CI $db returns self, so you can do:
public function get_jobs(string $lan):array
{
return $this->db->select()
->from('tbl_work_stats')
->join('tbl_work','tbl_work.id = tbl_work_stats.work_id')
->like('language',$lan)
->order_by('insertdate')
->get()
->result_array();
}

Laravel 5.6.8 Eloquent and many to many + many to one joins

I have many to many connect with between user - cityarea.
I have also area which connect cityarea (One cityarea can connect only one area).
I have this database structure:
users
id
username
password
cityareas
id
name
area_id
cityarea_user
id
cityarea_id
user_id
areas
id
name
Next I have Models
User
public function cityareas()
{
return $this->belongsToMany('App\Cityarea');
}
Cityarea
public function area()
{
return $this->belongsTo('App\Area');
}
public function users()
{
return $this->belongsToMany('\App\User');
}
Area
public function cityareas()
{
return $this->hasMany('App\Cityarea');
}
QUESTION:
How I can get all users where areas.name = "South" with Eloquent ?
Thanks!!
By using whereHas, you can do:
$users = User::whereHas('cityareas.area', function ($query) {
$query->where('name', 'South');
})->get();
Jeune Guerrier solution is perfect, but you can use with() method of eloquent If you also need cityarea collection along with users collection.
$users = User::with('cityareas')->whereHas('cityareas.area', function ($query) {
$query->where('name', 'South');
})->get();
This is exactly what the belongs to many relationships is built for.
You simply have to do, Cityarea::where('name', 'South')->first()->users;
If you want to do something further with the query, e.g. sort by users created at, you can do
Cityarea::where('name', 'South')->first()->users()->orderBy('creaated_at', desc')->get();
Note that if there is no such Cityarea with name 'South', the ->first() query above will return null and therefore will fail to fetch the users.
A more performant way to do it programmatically is to use the whereHas approach as discussed in the comments below.

Laravel. Eloquent query

I got this query:
$users = DB::table('users')->whereExists(function($query)
{
$query->select(DB::raw(1))
->from('galleries')
->whereRaw('galleries.user_id = users.id');
})->get();
This query selects all users who have gallery. Problem is that I can't use eloquent releationships now. Whenever i try to loop like this:
#foreach ($user->gallery as $gallery)
{{$gallery->name}}
#endforeach
I get error:
Undefined property: stdClass::$gallery
It happens with all other tables. What am I doing wrong here? My realationships are defined and they work just fine, i got problems only in this query. Thanks.
EDIT
Since it's not eloquent query, could you show me example how to write query, into few tables with eloquent. For example, I need all users who have their status approved in example table
First, determine a relationship in the User class, like this:
class User {
// Determine relation to Example table
public function examples() {
return $this->hasMany('Example', 'user_id', 'id'); // second parameter is the foreign key
}
}
Then the query:
User::whereHas('examples', function( $query ) {
$query->where('status','approved');
})->get();

Laravel, Datatables, column with relations count

I have two models, User and Training, with Many to many relationship between them. I'm using the Laravel Datatables package to display a table of all the users. This is how the data controller method (which retrieves the query results and creates a Datatables table) looks like:
public function getData()
{
$users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->remove_column('id')
->make();
}
How can I add a column to the created table which displays the total number of relations for each user (that is, how many Trainings does each User have)?
The brute force way would be to try a User::selectRaw(...) which has a built in subquery to get the count of trainings for the user and expose it as a field.
However, there is a more built-in way to do this. You can eager load the relationship (to avoid the n+1 queries), and use the DataTables add_column method to add in the count. Assuming your relationship is named trainings:
public function getData() {
$users = User::with('trainings')->select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->add_column('trainings', function($user) {
return $user->trainings->count();
})
->remove_column('id')
->make();
}
The name of the column in add_column should be the same name as the loaded relationship. If you use a different name for some reason, then you need to make sure to remove the relationship column so it is removed from the data array. For example:
return \Datatables::of($users)
->add_column('trainings_count', function($user) {
return $user->trainings->count();
})
->remove_column('id')
->remove_column('trainings')
->make();
Edit
Unfortunately, if you want to order on the count field, you will need the brute force method. The package does its ordering by calling ->orderBy() on the Builder object passed to the of() method, so the query itself needs the field on which to order.
However, even though you'll need to do some raw SQL, it can be made a little cleaner. You can add a model scope that will add in the count of the relations. For example, add the following method to your User model:
Note: the following function only works for hasOne/hasMany relationships. Please refer to Edit 2 below for an updated function to work on all relationships.
public function scopeSelectRelatedCount($query, $relationName, $fieldName = null)
{
$relation = $this->$relationName(); // ex: $this->trainings()
$related = $relation->getRelated(); // ex: Training
$parentKey = $relation->getQualifiedParentKeyName(); // ex: users.id
$relatedKey = $relation->getForeignKey(); // ex: trainings.user_id
$fieldName = $fieldName ?: $relationName; // ex: trainings
// build the query to get the count of the related records
// ex: select count(*) from trainings where trainings.id = users.id
$subQuery = $related->select(DB::raw('count(*)'))->whereRaw($relatedKey . ' = ' . $parentKey);
// build the select text to add to the query
// ex: (select count(*) from trainings where trainings.id = users.id) as trainings
$select = '(' . $subQuery->toSql() . ') as ' . $fieldName;
// add the select to the query
return $query->addSelect(DB::raw($select));
}
With that scope added to your User model, your getData function becomes:
public function getData() {
$users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->selectRelatedCount('trainings')
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->remove_column('id')
->make();
}
If you wanted the count field to have a different name, you can pass the name of the field in as the second parameter to the selectRelatedCount scope (e.g. selectRelatedCount('trainings', 'training_count')).
Edit 2
There are a couple issues with the scopeSelectRelatedCount() method described above.
First, the call to $relation->getQualifiedParentKeyName() will only work on hasOne/hasMany relations. This is the only relationship where that method is defined as public. All the other relationships define this method as protected. Therefore, using this scope with a relationship that is not hasOne/hasMany throws an Illuminate\Database\Query\Builder::getQualifiedParentKeyName() exception.
Second, the count SQL generated is not correct for all relationships. Again, it would work fine for hasOne/hasMany, but the manual SQL generated would not work at all for a many to many relationship (belongsToMany).
I did, however, find a solution to both issues. After looking through the relationship code to determine the reason for the exception, I found Laravel already provides a public method to generate the count SQL for a relationship: getRelationCountQuery(). The updated scope method that should work for all relationships is:
public function scopeSelectRelatedCount($query, $relationName, $fieldName = null)
{
$relation = $this->$relationName(); // ex: $this->trainings()
$related = $relation->getRelated(); // ex: Training
$fieldName = $fieldName ?: $relationName; // ex: trainings
// build the query to get the count of the related records
// ex: select count(*) from trainings where trainings.id = users.id
$subQuery = $relation->getRelationCountQuery($related->newQuery(), $query);
// build the select text to add to the query
// ex: (select count(*) from trainings where trainings.id = users.id) as trainings
$select = '(' . $subQuery->toSql() . ') as ' . $fieldName;
// add the select to the query
return $query->addSelect(DB::raw($select));
}
Edit 3
This update allows you to pass a closure to the scope that will modify the count subquery that is added to the select fields.
public function scopeSelectRelatedCount($query, $relationName, $fieldName = null, $callback = null)
{
$relation = $this->$relationName(); // ex: $this->trainings()
$related = $relation->getRelated(); // ex: Training
$fieldName = $fieldName ?: $relationName; // ex: trainings
// start a new query for the count statement
$countQuery = $related->newQuery();
// if a callback closure was given, call it with the count query and relationship
if ($callback instanceof Closure) {
call_user_func($callback, $countQuery, $relation);
}
// build the query to get the count of the related records
// ex: select count(*) from trainings where trainings.id = users.id
$subQuery = $relation->getRelationCountQuery($countQuery, $query);
// build the select text to add to the query
// ex: (select count(*) from trainings where trainings.id = users.id) as trainings
$select = '(' . $subQuery->toSql() . ') as ' . $fieldName;
$queryBindings = $query->getBindings();
$countBindings = $countQuery->getBindings();
// if the new count query has parameter bindings, they need to be spliced
// into the existing query bindings in the correct spot
if (!empty($countBindings)) {
// if the current query has no bindings, just set the current bindings
// to the bindings for the count query
if (empty($queryBindings)) {
$queryBindings = $countBindings;
} else {
// the new count query bindings must be placed directly after any
// existing bindings for the select fields
$fields = implode(',', $query->getQuery()->columns);
$numFieldParams = 0;
// shortcut the regex if no ? at all in fields
if (strpos($fields, '?') !== false) {
// count the number of unquoted parameters (?) in the field list
$paramRegex = '/(?:(["\'])(?:\\\.|[^\1])*\1|\\\.|[^\?])+/';
$numFieldParams = preg_match_all($paramRegex, $fields) - 1;
}
// splice into the current query bindings the bindings needed for the count subquery
array_splice($queryBindings, $numFieldParams, 0, $countBindings);
}
}
// add the select to the query and update the bindings
return $query->addSelect(DB::raw($select))->setBindings($queryBindings);
}
With the updated scope, you can use the closure to modify the count query:
public function getData() {
$users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
->selectRelatedCount('trainings', 'trainings', function($query, $relation) {
return $query
->where($relation->getTable().'.is_creator', false)
->where($relation->getTable().'.is_speaker', false)
->where($relation->getTable().'.was_absent', false);
})
->where('users.is_active', '=', 1);
return \Datatables::of($users)
->remove_column('id')
->make();
}
Note: as of this writing, the bllim/laravel4-datatables-package datatables package has an issue with parameter bindings in subqueries in the select fields. The data will be returned correctly, but the counts will not ("Showing 0 to 0 of 0 entries"). I have detailed the issue here. The two options are to manually update the datatables package with the code provided in that issue, or to not use parameter binding inside the count subquery. Use whereRaw to avoid parameter binding.
I would setup your DB tables and Eloquent models using the conventions provided at http://laravel.com/docs/4.2/eloquent. In your example you would have three tables.
trainings
training_user
users
Your models would look something like this.
class Training {
public function users() {
return $this->belongsToMany('User');
}
}
class User {
public function trainings() {
return $this->belongsToMany('Training');
}
}
You can then use Eloquent to get a list of users and eager load their trainings.
// Get all users and eager load their trainings
$users = User::with('trainings')->get();
If you want to count the number of trainings per user you can simply iterate over $users and count the size of the trainings array.
foreach ( $users as $v ) {
$numberOfTrainings = sizeof($v->trainings);
}
Or you can simply do it in pure SQL. Note that my example below assumes you follow Laravel's conventions for naming tables and columns.
SELECT
u.*, COUNT(p.user_id) AS number_of_trainings
FROM
users u
JOIN
training_user p ON u.id = p.user_id
GROUP BY
u.id
Now that you have a couple of ways to count the number of relations, you can use whatever method you like to store that value somewhere. Just remember that if you store that number as a value in the user table you'll need to update it every time a user creates/updates/deletes a training (and vice versa!).

Laravel - Query just one row in a pivot table and return all data within the relationship

Currently, a criteria BelongsToMany alerts and viceversa. They are related through a pivot table: criteria_id and alert_id.
I am getting all Criteria with the associated Alerts that belongs to the authenticated user, as such:
public function getMatches()
{
$matches = Criteria::whereUserId( Auth::id() )
->has('alerts')
->get();
}
This returns all associated results, whereas now, if a user picks a certain result, I want to be able to show just that. This is what I have so far:
Controller
public function getMatchDetails($alert_id, $criteria_id)
{
$matches = Alert::find($alert_id)
->has('criterias')
->where('criteria_id', $criteria_id)
->get();
}
Which is bringing over the correct variables, however, I am getting a MYSQL error:
Column not found: 1054 Unknown column 'criteria_id' in 'where clause'
select * from `alerts` where `alerts`.`deleted_at` is null and
(select count(*) from `criterias` inner join `alert_criteria` on `criterias`.`id` =
`alert_criteria`.`criteria_id` where `alert_criteria`.`alert_id` = `alerts`.`id`)
>= 1 and `criteria_id` = 7)
Any help would be hugely appreciated.
You could try something like this
public function getMatchDetails($alert_id, $criteria_id)
{
$match = Alert::whereHas('criterias', function ($q) use ($criteria_id) {
$q->where('criteria_id', $criteria_id);
})->find($alert_id);
}
Which will find the alert by id and also check that it has a relationship to criterias meeting those requirements.
I don't know if I understood well the question, but I'm going to try to answer
If you want to pass more than just a variable from the view to the controller, you can do something like this:
View
#foreach($matches as $match)
#foreach($match->alerts as $alert)
<td>{{$alert->pivot->criteria_id}}</td>
<td>{{$alert->id}}</td>
#endforeach
#endforeach
Controller
public function getMatchDetails($id, $var_2 = 0)
{
$myCriteriaIds = Criteria::whereUserId( Auth::id() )
->lists('id');
$match = Alert::find($id)->criterias()
->wherePivot('criteria_id', 'IN', $myCriteriaIds)
->get();
}
Route
Route::post('/users/alert/matches/{id}/{var_2}', array(
'as' => 'users-alert-matches',
'uses' => 'XXXController#getMatchDetails'
));

Categories