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

uzairusman1425's avatar

Get specific column of the foreign key table instead of pure foreign key

Hi everyone!

I have three tables: users, services, and job_posts, with the following structure:

users: id, full_name, role, email_verified_at

services: id, name

job_posts: id, user_id (fk), service_id (fk), title, created_at

Currently, I’m fetching jobs using the following Eloquent method:

JobPost::with('user', 'service')->get();

The result is something like this:

However, I want to fetch the user_name (i.e., full_name) and service_name (i.e., name) directly in the job_posts result, and not as nested objects. Ideally, the result should look like this:

{
    "data": {
        "jobs": [
            {
                "id": 17,
                "user_id": 112,
                "service_id": 1,
                "full_name": "Abdullah",
                "service_name": "Service name",
                "job_title": "Car Wash",
                "location": "Somewhere in world",
                "budget": 200,
                "status": "pending",
                "scheduled_time": "2024-11-06 15:24:30",
                "job_description": "Some description here",
                "created_at": "2024-11-26T13:48:16.000000Z",
                "updated_at": "2024-11-26T13:48:16.000000Z"
            }
        ]
    },
    "messages": []
}

I found a solution like this:

    ->join('services as s', 's.id', '=', 'jp.service_id')
    ->join('users as u', 'u.id', '=', 'jp.user_id')
    ->select('jp.*', 's.name as service_name', 'u.full_name as user_name')
    ->get();

However, it feels more like a raw SQL query, and I would prefer a more "Eloquent" approach that's easier to read and maintain. Is there a way to achieve this result using Eloquent while keeping it clean and expressive?

Thanks in advance!

0 likes
10 replies
Rebwar's avatar

@uzairusman1425 Here are some alternatives you can use:

Option-1

JobPost::with(['user:id,full_name', 'service:id,name'])
    ->get()
    ->map(function($job) {
        $job->user_name = $job->user->full_name;
        $job->service_name = $job->service->name;

        unset($job->user, $job->service);

        return $job;
    });

Option-2:

JobPost::with(['user', 'service'])
    ->addSelect([
        'user_name' => User::select('full_name')
            ->whereColumn('users.id', 'job_posts.user_id')
            ->limit(1),
        'service_name' => Service::select('name')
            ->whereColumn('services.id', 'job_posts.service_id')
            ->limit(1)
    ])
    ->get();
1 like
uzairusman1425's avatar

@Rebwar Thanks for your reply Rebwar! I have tried both options and achieved what I wanted but I wanted to know something about the performance optimization.

In option 1:

I think that we are fetching all of the data as I have mentioned in my question JobPost::with('user', 'service')->get(); and then just removing the extra data from the collection and setting user name and service right? So I think that in this option we have created 2 more steps instead of optimizing the db performance.

In option 2:

We are fetching all of the data from 3 tables and then just executing 2 more queries to fetch service_name and user_name

Option 3: Running the raw sql query like

DB::table('job_posts as jp')
        ->join('services as s', 's.id', '=', 'jp.service_id')
        ->join('users as u', 'u.id', '=', 'jp.user_id')
        ->select('jp.*', 's.name as service_name', 'u.full_name as user_name')
        ->get();

I wanna know how much performance difference it's gonna make and what would be the best approach.

Sorry I'm a newbie in Laravel. Any help will be very appreciated.

Thanks!

Rebwar's avatar
Rebwar
Best Answer
Level 32

@uzairusman1425 The first option gets all the data from the database using with(['user:id,name', 'service:id,name']) and then uses map() to add user_name and service_name to each job. While this method works, it can slow down if there are a lot of job posts because the processing happens in PHP after the data is fetched.

The second option is better because it uses addSelect to fetch user_name and service_name directly from the database. This reduces the need for extra processing. If you don't need the full user and service data, you can remove the with(['user', 'service']) part, which will make it even faster.

The third option is the fastest. It uses a single SQL query with joins to get all the needed data. This takes full advantage of the database's ability to handle relationships efficiently.

I tested all three options with 10,000 records and got these results:

Option 1 - Execution Time: 187.63303756714 ms
Option 2 - Execution Time: 96.518993377686 ms
Option 3 - Execution Time: 10.445833206177 ms

and if you remove the with(['user', 'service']) part from Option 2, the execution time will decrease to:: 47.78790473938 ms

1 like
uzairusman1425's avatar

@Rebwar Thanks for your precious time bro! Can you provide me the code you wrote for benchmarking the code execution if possible. It will be appreciated?

Rebwar's avatar

@uzairusman1425 I used PHP's microtime() to measure the execution time. But there are other ways to do it, like using the Laravel Debugbar package, which shows more details about query performance and other information.

1 like
Tray2's avatar

@uzairusman1425 as @rebwar suggests in option 2, you can define what you select from each table in the query. I would however nor recommend option 1, since it will take longer, and use more resources than letting the database handle it.

1 like
uzairusman1425's avatar

@Tray2 Thanks for your reply! As you have mentioned about the resource usage I thought about it and I have wrote some options in the Rebrew reply. Please have a look and guide me. Thanks!

Snapey's avatar

@uzairusman1425 the most performant is probably your initial query, but unless its thousands of rows the difference will be miniscule.

Go with option 2 with the subselects and drop the with statement as that is two unnecessary queries

1 like
Snapey's avatar

You dont say what your reasons are for flattening the structure, but if its just to reduce data size, I would

JobPost::with('user:id,full_name', 'service:id,service_name')->get();

just wanted to make sure you knew that limiting the columns was an option

1 like

Please or to participate in this conversation.