Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Deekshith's avatar

How to convert query in single query with join

i have a query like below,

$testsdata = Test::where('tests.course',$course_det->course_id)
                        ->whereIn('test_id',$mappedtestids)
                        ->whereIn('tests.test_type',['Integrated Mains','Mains']);

        return $testsdata = $testsdata->get()->map(function($test) use($from_date,$to_date){
            $coursename = $test->coursedet ? $test->coursedet->course_name : '';

            $totals = DB::table('upload_answers')->where('test_id',$test->test_id)
                    ->whereBetween('created_at', [$from_date, $to_date])
                    ->selectRaw('count(*) as total')
                    ->selectRaw('count(case when upload_answers.status = 0 then 1 end) as new_copies')
                    ->selectRaw('count(case when upload_answers.evaluated_pdf IS NOT NULL then 1 end) as evaluated_copies')
                    ->selectRaw('count(case when upload_answers.evaluated_pdf IS NULL then 1 end) as pending_copies')
                    ->first();

            return [
                'test_name' => $test->test_name,
                'test_code' => $test->test_code,
                'coursename' => $coursename,
                'new_uploads' => $totals->new_copies,
                'total_uploads' => $totals->total,
                'pending_copies' => $totals->pending_copies,
                ];

        });

this is working fine. i tried withCount for hasMany relationship. but withCount was very slow and it was taking around 15 to 20 seconds to execute and above query executing in 5 seconds. but how to improvise above query as right now i have written this aggregate count query inside the map instead can i write this in leftjoin any idea?

0 likes
7 replies
SilenceBringer's avatar
Level 55

@deekshith something like (assuming tests.test_id is primary or unique key)

$testsdata = Test::where('tests.course',$course_det->course_id)
	->whereIn('test_id',$mappedtestids)
	->whereIn('tests.test_type',['Integrated Mains','Mains'])
	->join('upload_answers', fn ($join) => $join->on('upload_answers.test_id', 'tests.test_id')
		->whereBetween('upload_answers.created_at', [$from_date, $to_date])
	)
	->select(
		'tests.*',
		DB::raw('count(upload_answers.id) as total'),
		DB::raw('count(case when upload_answers.status = 0 then 1 end) as new_copies'),
		DB::raw('count(case when upload_answers.evaluated_pdf IS NOT NULL then 1 end) as evaluated_copies'),
		DB::raw('count(case when upload_answers.evaluated_pdf IS NULL then 1 end) as pending_copies')
	)
	->groupBy('tests.test_id')
	->with('coursedet')
	->get()
	->map(fn ($test) => [
		'test_name' => $test->test_name,
		'test_code' => $test->test_code,
		'coursename' => optional($test->coursedet)->course_name,
		'new_uploads' => $test->new_copies,
		'total_uploads' => $test->total,
		'pending_copies' => $test->pending_copies,
	]);
Deekshith's avatar

@SilenceBringer Thank you so much for the reply and also here i want to to fetch the details of upload_answers which if join again then it will slow down the query. i want object data along with count. any suggestion?

Example. test has many uploaded_answers so right now i am getting the count of it. now i want to fetch the uploaded_answers details which has user_id so i want to join users table and get user details in object. it would be really helpful if i get some idea on this.

SilenceBringer's avatar

@Deekshith I think the better way here is to eager load related uploaded_answers constrained by user

->with(['coursedet', 'upload_answers' => fn ($query) => $query->where('user_id', $userId)])
Deekshith's avatar

@SilenceBringer i am trying like below,

return $testsdata = Test::with(['uploadcopydata'=> function($query) use($from_date,$to_date){
            $query ->join('users', 'upload_answers.user_id', '=', 'users.user_id')->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            ->whereNotNull('evaluated_pdf')
            ->select('users.name','users.email');
            
            }])->where('tests.course',$course_det->course_id)
            ->whereIn('tests.test_id',$mappedtestids)
            ->whereIn('tests.test_type',['Integrated Mains','Mains'])
            ->join('upload_answers', fn ($join) => $join->on('upload_answers.test_id', 'tests.test_id')
                ->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            )
            ->join('users','upload_answers.user_id','=','users.user_id')
            ->select(
                'tests.*','users.name','users.email',
                DB::raw('count(upload_answers.id) as total'),
                DB::raw('count(case when upload_answers.status = 0 then 1 end) as new_copies'),
                DB::raw('count(case when upload_answers.evaluated_pdf IS NOT NULL then 1 end) as evaluated_copies'),
                DB::raw('count(case when upload_answers.evaluated_pdf IS NULL then 1 end) as pending_copies'),
            )
            ->groupBy('tests.test_id')
            ->with('coursedet')
            ->get()
            ->map(fn ($test) => [
                'test_name' => $test->test_name,
                'test_code' => $test->test_code,
                'coursename' => optional($test->coursedet)->course_name,
                'new_uploads' => $test->new_copies,
                'total_uploads' => $test->total,
                'pending_copies' => $test->pending_copies,
                'uploadcopies' => $test->uploadcopydata,
            ]);

can i use join inside the relationsip "with" ? if i dont use select then it is working fine but i want to get only user name and email so i have used select but it is showing empty.

Deekshith's avatar

@silencebringer Thank you for the help, it has been solved and updated query is,

return $testsdata = Test::with(['evaluatedcopies'=> function($query) use($from_date,$to_date){
            $query->join('users', 'upload_answers.user_id', '=', 'users.user_id')->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            ->select('upload_answers.*','users.name','users.email');
            
            },'evaluatedpendingcopies'=> function($query) use($from_date,$to_date){
            $query->join('users', 'upload_answers.user_id', '=', 'users.user_id')->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            ->select('upload_answers.*','users.name','users.email');
            
            }, 'newmainsupload'=> function($query) use($from_date,$to_date){
            $query->join('users', 'upload_answers.user_id', '=', 'users.user_id')->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            ->select('upload_answers.*','users.name','users.email');
            
            }

            ])->where('tests.course',$course_det->course_id)
            ->whereIn('tests.test_id',$mappedtestids)
            ->whereIn('tests.test_type',['Integrated Mains','Mains'])
            ->join('upload_answers', fn ($join) => $join->on('upload_answers.test_id', 'tests.test_id')
                ->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            )
            ->join('users','upload_answers.user_id','=','users.user_id')
            ->select(
                'tests.*','users.name','users.email',
                DB::raw('count(upload_answers.id) as totalcount'),
                DB::raw('count(case when upload_answers.status = 0 then 1 end) as newcount'),
                DB::raw('count(case when upload_answers.evaluated_pdf IS NOT NULL then 1 end) as evaluatedcount'),
                DB::raw('count(case when upload_answers.evaluated_pdf IS NULL then 1 end) as pendingcount'),
            )
            ->groupBy('tests.test_id')
            ->with('coursedet')
            ->get()
            ->map(fn ($test) => [
                'test_name' => $test->test_name,
                'test_code' => $test->test_code,
                'coursename' => optional($test->coursedet)->course_name,
                'new_uploads' => $test->new_copies,
                'totalcount' => $test->totalcount,
                'newcount' => $test->newcount,
                'evaluatedcount' => $test->evaluatedcount,
                'pendingcount' => $test->pendingcount,
                'evaluatedcopies' => $test->evaluatedcopies,
                'evaluatedpendingcopies' => $test->evaluatedpendingcopies,
                'newmainsupload' => $test->newmainsupload,
            ]);

Test.php Model

public function newmainsupload()
    {
        return $this->hasMany('App\UploadAnswer','test_id','test_id')->where('status',0);
    }

    public function evaluatedcopies()
    {
        return $this->hasMany('App\UploadAnswer','test_id','test_id')->whereNotNull('evaluated_pdf');
    }

    public function evaluatedpendingcopies()
    {
        return $this->hasMany('App\UploadAnswer','test_id','test_id')->whereNull('evaluated_pdf');
    }

    public function uploadcopies()
    {
        return $this->hasMany('App\UploadAnswer','test_id','test_id');
    }

as of now it greatly improved in execution time. any suggestions from your end? this is fine? Once again thank you for the help as it helped me to optimize the query.

SilenceBringer's avatar

@Deekshith

with(['evaluatedcopies'=> function($query) use($from_date,$to_date){
            $query->join('users', 'upload_answers.user_id', '=', 'users.user_id')->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            ->select('upload_answers.*','users.name','users.email');
            
            },'evaluatedpendingcopies'=> function($query) use($from_date,$to_date){
            $query->join('users', 'upload_answers.user_id', '=', 'users.user_id')->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            ->select('upload_answers.*','users.name','users.email');
            
            }, 'newmainsupload'=> function($query) use($from_date,$to_date){
            $query->join('users', 'upload_answers.user_id', '=', 'users.user_id')->whereBetween('upload_answers.created_at', [$from_date, $to_date])
            ->select('upload_answers.*','users.name','users.email');
            
            }

            ])

you load data from the same table 3 times. Instead you can load it once

with(['uploadcopies'])

and then filter results

                'evaluatedcopies' => $test->uploadcopies->filter(fn ($item) => $item->evaluated_pdf)->values(),
                'evaluatedpendingcopies' => $test->uploadcopies->filter(fn ($item) => !$item->evaluated_pdf)->values(),
                'newmainsupload' => $test->uploadcopies->filter(fn ($item) => !$item->status)->values(),

Please or to participate in this conversation.