Joining 3 tables and compute average of a column - php

I am currently learning Laravel and I am using DB class when joining the three tables. I am able to join the three tables but I need to get the average of a certain column for a teacher (ratings table, rating column), here's what I have and I am stuck here.
Here's my table design
And here's my query
$teachers = DB::table('ratings as r')
->join('users as u','r.teacher_id', '=', 'u.id')
->join('user_infos as ui','r.teacher_id', '=', 'ui.user_id')
->select('r.rating','ui.about','ui.first_name','ui.last_name','ui.avatar','ui.rate','u.*')
->where('u.status', 1)->get();
Also, the results for same user is being repeated. The user has two ratings from the ratings table and it appear two times in my view.
What I want to display here is the list of all teachers and in each card, with their corresponding ratings.. so if I have two teachers in the table, it will display the two teachers and on the right top side of the card is their rating.

Here is a possible solution:
$teachers = DB::table('ratings as r')
->join('users as u','r.teacher_id', '=', 'u.id')
->join('user_infos as ui','r.teacher_id', '=', 'ui.user_id')
->select(DB::raw('AVG(r.rating) as average_rating'),'ui.about','ui.first_name','ui.last_name','ui.avatar','ui.rate','u.*')
->groupBy('r.teacher_id')
->where('u.status', 1)->get();

Ok..since you are using Laravel naming convention/recommendation, I think would be easier/cleaner if you use Eloquent.
I'm not sure if you already created the Eloquent models. And, because of that, I'll put everything here (models, etc).
Ratings model
class Rating extends Model
{
protected $guard = ['id'];
public function teacher()
{
return $this->belongsTo(User::class, 'teacher_id');
}
public function student()
{
return $this->belongsTo(User::class, 'student_id');
}
}
User info model
class UserInfo extends Model
{
protected $guard = ['id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
User model
class User extends Model
{
protected $guard = ['id'];
public function ratings()
{
return $this->hasMany(Rating::class, 'teacher_id');
}
public function infos()
{
return $this->hasMany(UserInfo::class);
}
}
Possible query solution for your problem:
$ratings = Rating::with(['teacher.infos', 'student.infos'])->whereHas('teacher', function($q) {
$q->where('status', true);
})->get();
This will probably give you something like this:
// ratings: [
// "id": 1,
// "teacher_id": 1,
// "student_id": 2,
// ....
// "teacher": {
// "id": 1,
// "name": "...."
// ...
// "infos": [{
// "id": 1,
// "skype": '....'
// ....
// }]
// },
// "student": {
// "id": 2,
// "name": ....,
// "infos": [{
// "id": ...
// }]
// }
// ]
Now you have a collection of Ratings. And, if you need to access the user or the user info, you just need to
// Example:
$firstStudentInfo = $ratings->isEmpty() ? null : $ratings->first()->student->infos;
If you need to calculate something, you can either use an extra query (db) or just a single method on your
collection. I think, in this case, a collection can be faster. You can also create a specific collection
for your "Ratings" model, with specific calculations ("RatingsCollection").
Another example (blade template). Since we already loaded everything "eager load", we don't need to worry
with N+1 query problems here. (https://laravel.com/docs/5.5/eloquent-relationships#eager-loading)
// Ratings grouped by teacher
#foreach ($ratings->groupBy('teacher') as $rating)
Teacher: {{ $rating->avg()...}} or whatever..
#endforeach
If you still want to use DB, #Md Mahfuzur Rahman will do the trick.
:)

Related

Laravel Eloquent Relationship - not fetching based on condition

I wanted to fetch all id & name from User table which satisfies the condition , order_userId of Orders table == id of User table and order & uploadId columns of Orders table has the values false & null respectively. The below code returns all rows if data from User table without checking the where condition i've specified.. How can i fix this?
$data = User::with(['orders'=>function ($query){
$query->where('order', false);
$query->whereNull('uploadId');
}])->pluck('name', 'id');
User Model
public function orders()
{
return $this->belongsTo(Orders::class, 'order_userId', 'id');
}
The above code gives an output as below:
{
"id": 2,
"name": "jen",
"orders": null
},
{
"id": 3,
"name": "jos",
"orders": null
}
Try
$data = User::whereHas('orders', function ($query) {
$query->where('order', false);
$query->whereNull('uploadId');
})->pluck('name', 'id');
try This Solution:
The right relation is that, User has many orders.
User Model:
public function orders()
{
return $this->hasMany(Orders::class, 'order_userId', 'id');
}
Checkout this also..
$data = User::whereHas('orders', function ($query) {
$query->where([['order', false],['uploadId','>',0]]);
})->pluck('name', 'id');

Laravel eloquent get relation count

I use Laravel 5.3.
I have 2 tables :
Articles
---------
id
cat_id
title
And
Category
---------
id
parent_id
title
I have defined my relations in my models :
// Article model
public function category()
{
return $this->belongsTo(Category::class);
}
// Category model
public function children()
{
return $this->hasMany(Category::class, 'parent_id', 'id');
}
Is there an easy way using Eloquent to have a list a categories with count of articles. The difficulty is that I want to group categories where id_parent = 0, i.e. I want to display only parent categories with count of articles in children.
I tried something like that :
$category = new \App\Models\Category();
$categoryTable = $category->getTable();
return $category->leftJoin('article', 'article.cat_id', '=', 'category.id')
->whereIn('article.cat_id', function($query)
{
$query->select('cat_id')
->from('categories')
->where('categories.parent_id', ???)
->orWhere($this->tableName .'.cat_id', $id);
})
->groupBy('cat_id');
But I am lost...
you can use withCount(). It is available from 5.3 version
for more info about eloquent visit : https://laravel.com/docs/5.3/eloquent-relationships
Define a articles() relation in your Category model as:
public function articles()
{
return $this->hasMany(Article::class, 'cat_id');
}
Then you can try it as:
Category::where('parent_id', 0)->withCount('articles')->get();
You can use the hasManyThrough() Eloquent method to fetch all of the childrens' Articles, then add the article counts together in a nice little getter. I added the getter to the $appends array on the model to help illustrate it in the Tinker output.
class Category extends Model
{
protected $appends = [
'articleCount'
];
public function articles()
{
return $this->hasMany(Article::class);
}
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
public function childrenArticles()
{
return $this->hasManyThrough(Article::class, Category::class, 'parent_id');
}
public function getArticleCountAttribute()
{
return $this->articles()->count() + $this->childrenArticles()->count();
}
}
Here's the Tinker output:
Psy Shell v0.8.0 (PHP 7.0.6 — cli) by Justin Hileman
>>> $cat = App\Category::first();
=> App\Category {#677
id: "1",
name: "Cooking",
parent_id: null,
created_at: "2016-12-15 18:31:57",
updated_at: "2016-12-15 18:31:57",
}
>>> $cat->toArray();
=> [
"id" => 1,
"name" => "Cooking",
"parent_id" => null,
"created_at" => "2016-12-15 18:31:57",
"updated_at" => "2016-12-15 18:31:57",
"articleCount" => 79,
]
>>>
If you want to restrict your Category query to ones that have children that have articles, you could do that using the has() method:
Category::has('children.articles')->get();
Here's more on the has() method:
https://laravel.com/docs/5.3/eloquent-relationships#querying-relationship-existence
And the hasManyThrough() method:
https://laravel.com/docs/5.3/eloquent-relationships#has-many-through
This should work:
$category
->where('categories.parent_id', 0)
->leftJoin('article', 'article.cat_id', '=', 'categories.id')
->select('categories.id', \DB::raw('COUNT(article.id)'))
->groupBy('categories.id')
->get();
The above query will get you category IDs and count of all articles that belong to the category.
After reading your question and comments again, if I understand correctly you want to get the count of all articles that belong to those categories (with parent_id = 0) + the count of articles that belong to sub categories (those with parent_id = (id of some category)).
Now I have no way of testing this easily, but I think something along these lines should work for that.
$category
->where('categories.parent_id', 0)
->leftJoin('article', 'article.cat_id', '=', 'categories.id')
->leftJoin('categories as c2', 'c2.parent_id', '=', 'categories.id')
->leftJoin('article as a2', 'a2.cat_id', '=', 'c2.id')
->select('categories.id', \DB::raw('(COUNT(article.id)) + (COUNT(a2.id)) as count'))
->groupBy('categories.id')
->get();
That beign said, I think you're better of having a column named count in categories and update it each time a new article gets added. For performance.
public function NoOfStudent()
{
return $this->hasMany(UserAssignment::class,'assignment_id','id');
}
$assignment = Assignment::select('id','batch_id','title','description','attachment','last_submission_date',DB::raw('(CASE WHEN type = 9 THEN "Quiz Type" ELSE "Descriptive" END) AS assignment_type'),DB::raw('(CASE WHEN status = 1 THEN "Assigned" ELSE "Not Assigned" END) AS status'))
->with('assignmentBatch:id,batch_number')
->where('assignments.instructor_id',auth('api')->user()->id)
->orderBy('created_at','DESC');
if(!$request->user_id){
$assignment =$assignment->withCount('NoOfStudent');
}
In regards to Carlos_E.'s answer.
You can improve the query by using whereHas instead of using whereIn:
$agents = Agents::whereHas('schedule')
)->with('schedules')->get();
I am sure somebody is still going through this, I was able to solve it the following way, suppose I have an Agent model and a Schedule model, i.e. one agent may have many schedules:
class Schedule extends Model {
public function agent() {
return $this->belongsTo(Agent::class, 'agent_id');
}
}
class Agent extends Model {
public function user(){
return $this->belongsTo(User::class);
}
public function schedules(){
return $this->hasMany(Schedule::class);
}
}
Well some agents may not necessarily have schedules assigned, thus, I filtered those before calling the with() method, like this:
$agents = Agents::whereIn(
'id',
Schedule::distinct()->pluck('agent_id')
)->with('schedules')->get();
Hope this helps!.

Laravel Datatables Sorting Appended Fields

My Model Items is related to Rooms, which is related to Buildings, which is related to Locations.
Short: Items belongsTo Rooms belongsTo Buildings belongsTo Locations
At the index function of the ItemController I want to show a table of Items. I use Laravel Datatables. It works for 'simple' tables, but I ran into the problem of sorting/searching custom/appended fields, because they are not in the table of course.
Basically I want to join the Room, Building and Location Name for each Item in the displaying table.
This is my Items Model:
protected $appends = [
'building_title',
'location_title',
'room_title',
];
public function getLocationTitleAttribute() {
return $this->room->building->location->title;
}
public function getBuildingTitleAttribute() {
return $this->room->building->title;
}
public function getRoomTitleAttribute() {
return $this->room->title;
}
This is my Controller:
public function anyData()
{
return Datatables::of(Item::query())->make(true);
}
What is the best approach to enable sorting/filtering for appended fields?
Thank you for reading.
This is my solution (no appended fields were used):
public function anyData()
{
$items = Item::join('rooms', 'items.room_id', '=', 'rooms.id')
->join('buildings', 'rooms.building_id', '=', 'buildings.id')
->join('locations', 'buildings.location_id', '=', 'locations.id')
->select([
'items.id',
'items.title',
'items.label_number',
'items.fibu_number',
'rooms.title as room_title',
'buildings.title as building_title',
'locations.title as location_title'
]);
return Datatables::of($items)->make(true);
}

Laravel leftJoin not return correctly

I have a problem with laravel select using leftJoin. I'm trying to select 2 posts and count how many comments there is(first post have 7 comments, second - 0), but I got only first post with 7 comments.
Code is:
$posts = DB::table('posts')
->leftJoin('comments', 'comments.post', '=', 'posts.id')
->select(DB::raw('posts.title, posts.body, posts.created_at, posts.slug, CASE WHEN comments.post IS NULL THEN 0 WHEN comments.post IS NOT NULL THEN count(comments.post) END as count'))
->get();
And when I trying to check what i see in web browser i got error:
Call to a member function count() on a non-object
This error in my view file at line where i using #if($posts->count())
I have debugged that i got only one post from using print_r().
Any suggestions?
I think your best bet here is to use some of the built in functionality of laravel's Eloquent ORM.
set up a relationship in the models:
Post.php:
<?php
class Post extends Eloquent {
protected $table = 'posts';
public function comments()
{
return $this->hasMany('Comment', 'posts');//first param refrences the other model, second is the foreign key
}
Comment.php:
<?php
class Comment extends Eloquent {
protected $table = 'comments';
public function comments()
{
return $this->belongsTo('Post');//first param refrences the other model, second is unnecessary if you are using auto incrementing id
}
now you have a relationship set up and there is no need for the join.
Usage:
there may be a better way to do this, but this should work.
$posts = Post::with('comments')->get();//retrieves all posts with comments
foreach($posts as $post){
$count = count($post['comments']);
$post['comment_count'] = $count;
}
return $posts;
this will return a result that contains all of the posts, with a field called 'comments' that contains an array of all of the comments related. the 'comment_count' field will contain the count.
example:
[
{
"id": 1,
"created_at": "2014-07-02 11:34:00",
"updated_at": "2014-07-02 11:34:00",
"post_title": "hello there",
"comment_count": 1,
"comments": [
{
"id":'blah'
"comment_title":"blah"
}
]
}
you can now pass this to your view and loop through each post and get the $post['comment_count']

Creating a query in Laravel by using `with` and `where`

I'm wondering it would be possible to add a where condition to a with.
Such as:
Comment::with('Users')->where('allowed', 'Y')->get();
I was trying to find a more simple way to make queries avoiding the whereHas method which looks quite verbose:
$users = Comment::whereHas('users', function($q)
{
$q->where('allowed', 'Y');
})->get();
The raw query I want internally to generate should be like so:
select * from comments, users
where users.id = comments.user_id and
users.allowed = 'Y'
I'm used to work with CakePHP in which this queries look very simple:
$this->Comments->find('all', array('Users.allowed' => 'Y'));
The relationships I have defined are:
//Comments.php
public function Users()
{
return $this->belongsTo('Users');
}
//Users.php
public function Comments(){
return $this->hasMany('Comments');
}
You may try this
$users = User::with(array('comments' => function($q)
{
$q->where('attachment', 1);
}))->get();
Update : Alternatively you may use a where clause in your relationship in your User model
// Relation for comments with attachment value 1
// and if hasMany relation is used
public function commentsWithAttachment()
{
return $this->hasMany('Comment')->where('attachment', 1);
}
// Relation for all comments
// and if hasMany relation is used
public function comments()
{
return $this->hasMany('Comment');
}
So, you can just use
// Comments with attachment value 1
User::with('commentsWithAttachment')->get();
// All comments
User::with('comments')->get();
Update : I think you want all comments with users where attachment is 1, if this what you want then it should be Comment not User
Comment::with('user')->where('attachment', 1)->get();
In this case your relation should be
public function user()
{
return $this->belongsTo('User'); // if model name is User
}
Because one comment belongs to only one user.

Categories