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

orest's avatar
Level 13

conditional relationship / query based on table field

I have the following tables

categories
- id
- parent_id

threads
- id
- title
- category_id
- updated_at

The categories table consists of children and parent categories and each thread belongs to a children category.

For each category ( either parent or children ) i want to fetch the thread that was most recently updated. Therefore, when i query all categories, i need to have an extra field which contains the title of that thread.

To better illustrate this, i will give an example.

  • parent_categoty = iOS
  • children_category = iOS13
  • children_category = iOS14

Now when i fetch all categories (iOS, iOS13 and iOS14 ), i want to have an extra field named 'recentThread'. However since threads belong to children_categories, the parent_category doesn't have any thread associated with it, but instead for the parent_category i want to fetch the most recent thread among its children categories.

Therefore i need to use a different relationship based on whether the parent_id is NULL.

public method recentThreadForParent(){

return $this->hasOneThrough(
            Thread::class,
            Category::class,
            'parent_id',
            'category_id'
        )->latest('updated_at');
}
public method recentThreadForChildren(){
 return $this->hasOne(Thread::class)->latest('updated_at');
}

After i did a research i found the following

public method scopeWithRecentTrhead($query){

 if ($this->parent_id == null ){

  // do something
}
else {

 // do something else
}
}

But it is always null so it enters always the firs condition.

On top of that i want in the end to have the same name for either relationship in order to be able to access the recent thread like this

$category->recentThread

Where category can be either parent or children. The solution that i found for this is to use addSelect

public method scopeWithRecentThread($query){

// For the children category
 $query->addSelect([
 'recentThread' => Thread::select('title')
                   ->whereColumn('category_id', 'categories.id')
                   ->orderBy('updated_at', 'desc')
                   ->limit(1)
 ]);

// For the parent category im not sure how to do it because i need the ids of the children for each parent category. ( this is the hasOneThrough relationship )

 $query->addSelect([
 'recentThread' => Thread::select('title')
                   ->whereIn('category_id', 'Ids of children')
                   ->orderBy('updated_at', 'desc')
                   ->limit(1)
 ]);

}

and in that case i don't know how to conditionally execute a different query. i've tried to use when but what i need to achieve is something like

$query->when('parent_id is NOT NULL') 

But that doesn't work either

0 likes
9 replies
guybrush_threepwood's avatar

Hello @orest

Maybe I'm not understanding correctly, but can't you use a single relationship and switch the return based on the parent_id value?

Something like:

public method recentThread() {

    if ( is_null($this->parent_id) ) {
        return $this->hasOneThrough(
            Thread::class,
            Category::class,
            'parent_id',
            'category_id'
        )->latest('updated_at');
    }

    return $this->hasOne(Thread::class)->latest('updated_at');
}
1 like
orest's avatar
Level 13

hi @guybrush_threepwood

I've tried that but the problem is that

$this->parent_id 

always returns null for some reason and thus it enters the the if statement

guybrush_threepwood's avatar

Have you tried checking for null in the relationship method?

In your example you check for null in a scope, that has a completely different outcome.

1 like
orest's avatar
Level 13

@guybrush_threepwood

I just tried it again and for the parent_categories the recentThread has the proper value but for the children_categories the recentThread is null because as i said it enters the if statement always.

Could you please explain me though why is it different in a relationship and in query scope ? Since in both cases $this refers to the query builder i guess ( or more specifically $this refers to the Categories table if i am not mistaken )

guybrush_threepwood's avatar

When you apply a scope in order to filter your results you're just calling a static method on an empty eloquent model (thus, the $this->parent_id property is always going to be null).

Check out the examples in the documentation: https://laravel.com/docs/7.x/eloquent#query-scopes Notice how they only reference the scope parameters and not model properties (since that doens't make sense).

When you query your models first, and then call the relationship, the model's parent_id property will be set to the correct value since the models will be populated from the database.

Try adding the relationship code and then adding the follow code to your controller and pass the $categories variable to your view:

$categories = Category:all();

And then add to the view:

<ul>
@foreach($categories as $category)
    <li>Category name: {{ $category->name }} | Latest Thread: {{ $category->recentThread->title }}</li>
@endforeach
<ul>

PS: Replace $category->name with your actual column.

1 like
orest's avatar
Level 13

@guybrush_threepwood

that works but i didn't consider that approach because i want to eager-load the relationship.

So ultimately i need to do

Category::with('recentThread')->get();

And in this case

$this->parent_id

returns always null.

1 like
guybrush_threepwood's avatar
Level 33

I understand.

This is how you would achieve that with RAW SQL:

SELECT c.id AS category_id, c.parent_id, name AS category_name,
IF(parent_id IS NOT NULL, t1.id, t2.id) AS thread_id,
IF(parent_id IS NOT NULL, t1.title, t2.title) AS thread_title
FROM categories c
LEFT JOIN threads t1 ON t1.id = (
  SELECT id
  FROM threads
  WHERE category_id=c.id
  ORDER BY updated_at DESC
  LIMIT 1
)
LEFT JOIN threads t2 ON t2.id = (
  SELECT id
  FROM threads
  WHERE category_id IN (
    SELECT id
    FROM categories
    WHERE parent_id = c.id
  )
  ORDER BY updated_at DESC
  LIMIT 1
)
ORDER BY c.id ASC;

Demo: http://sqlfiddle.com/#!9/5839cc/27

No idea how or if it's possible to achieve with the QueryBuilder. Surely not with Eloquent.

Good luck!

Please or to participate in this conversation.