Try something like this
Model::orderByRaw('coalesce(parent_id, id), parent_id is not null, title')->get();
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
I've got a database table, with a bunch of columns, the ones I'm trying to order by are 'id', 'parent_id' and 'title'.
Thing is, I want the result to be something like:
title:A | id:1 | parent_id:null
title:E| id:5 | parent_id:1 <= here because parent_id = id from previous
title:B | id:2 | parent_id:null
title:C | id:3 | parent_id:null
title:D | id:6 | parent_id:3 <= here because parent_id = id from previous, but above id:4 due to title alphabetically
title:F | id:4 | parent_id:3 <= here because parent_id = id from previous, but under id:6 due to title alphabetically
A simple
return Model::orderBy('parent_id', 'asc')->orderBy('id', 'asc')->orderBy('title', 'asc')->paginate(10);
obviously doesn't do the trick.
Been search around, sadly can't find a solution or documentation.
Any push in the right direction is much appreciated.
@tikay , indeed @michaloravec suggestion on using relationships seems to be a more Laravel-y solution.
Maybe I was excited on revisiting CTE queries that I jumped straight to them. I've used them a lot prior in my career when the company I worked for only allowed us to code on Microsoft SQL Server or Excel (not an IT company, I was a BI analyst there, some companies here restrict which software you can install in a company's computer).
Using relationships would still have the need to present the data hierarchically where each nested set of children nodes needs to be presented close to its parent.
Also Laravel would make as many requests as the deepest level you need to fetch (probably more than the amount of levels as levels go deeper).
<?php
// ./routes/web.php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Route;
class Model extends \Illuminate\Database\Eloquent\Model
{
// protected $table = 'models'; // Table name guessed by Laravel
public function children()
{
return $this->hasMany(Model::class, 'parent_id');
}
public function childrenWithChildren()
{
return $this->hasMany(Model::class, 'parent_id')
->with(['childrenWithChildren']) // recursive relation
->orderBy('title');
}
public function scopeRootNodes(Builder $builder)
{
$builder->whereNull('parent_id');
}
}
Route::get('/related', function () {
return Model::query()
->with(['childrenWithChildren'])
->rootNodes()
->orderBy('title')
->get();
});
Note that here, as in my previous code sample, I am defining the model directly in the ./routes/web.php file for simplicity. In your app move the model code to its own class in the ./app/Models/ folder and consider using a controller for your route.
The result will be:
[
{
"id": 1,
"parent_id": null,
"title": "A",
"children_with_children": [
{
"id": 5,
"parent_id": 1,
"title": "E",
"children_with_children": [
{
"id": 8,
"parent_id": 5,
"title": "C",
"children_with_children": [
{
"id": 10,
"parent_id": 8,
"title": "C",
"children_with_children": []
}
]
},
{
"id": 7,
"parent_id": 5,
"title": "D",
"children_with_children": []
}
]
}
]
},
{
"id": 3,
"parent_id": null,
"title": "B",
"children_with_children": [
{
"id": 6,
"parent_id": 3,
"title": "D",
"children_with_children": []
},
{
"id": 4,
"parent_id": 3,
"title": "F",
"children_with_children": []
}
]
},
{
"id": 2,
"parent_id": null,
"title": "C",
"children_with_children": []
},
{
"id": 9,
"parent_id": null,
"title": "C",
"children_with_children": [
{
"id": 11,
"parent_id": 9,
"title": "B",
"children_with_children": []
}
]
}
]
First thought: much easier! And indeed it is much easier.
There are some few concepts to learn about for a new comer, such as query scopes and relationships that will be more useful to add to your Laravel tool belt than learning SQL Recursive CTEs. Maybe the childrenWithChildren relation is a bit weird as it references itself on its definition, but much less complicated than the odd SQL syntax.
Also results are the desired order, great!
Problems arise when you want to present these results. You might have noted the child records are nested within their parent records.
I don't know how to it without recursion. @michaloravec if you know how to traverse this tree-like result without recursion I would like to learn. Asking genuinely, please don't think I am being picky about it, you are one of the forums members which responses and contributions I really appreciate and learn from them.
As I just said, the way I know to flatten this nested set is by using recursion. Let's see a way to do it:
<?php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Route;
class Model extends \Illuminate\Database\Eloquent\Model
{
// protected $table = 'models'; // Table name guessed by Laravel
public function children()
{
return $this->hasMany(Model::class, 'parent_id');
}
public function childrenWithChildren()
{
return $this->hasMany(Model::class, 'parent_id')
->with(['childrenWithChildren']) // recursive relation
->orderBy('title');
}
public function scopeRootNodes(Builder $builder)
{
$builder->whereNull('parent_id');
}
}
Route::get('/related', function () {
return Model::query()
->with(['childrenWithChildren'])
->rootNodes()
->orderBy('title')
->get()
->flatMap($flatten = function (Model $model) use (&$flatten) {
$children = $model->childrenWithChildren;
$model->unsetRelation('childrenWithChildren');
return [$model, ...$children->flatMap($flatten)];
});
});
And the results:
[
{
"id": 1,
"parent_id": null,
"title": "A"
},
{
"id": 5,
"parent_id": 1,
"title": "E"
},
{
"id": 8,
"parent_id": 5,
"title": "C"
},
{
"id": 10,
"parent_id": 8,
"title": "C"
},
{
"id": 7,
"parent_id": 5,
"title": "D"
},
{
"id": 3,
"parent_id": null,
"title": "B"
},
{
"id": 6,
"parent_id": 3,
"title": "D"
},
{
"id": 4,
"parent_id": 3,
"title": "F"
},
{
"id": 2,
"parent_id": null,
"title": "C"
},
{
"id": 9,
"parent_id": null,
"title": "C"
},
{
"id": 11,
"parent_id": 9,
"title": "B"
}
]
Great! In the order we wanted! And around 5-6 lines of code shorter then the raw SQL version.
although that is debatable as we could write the SQL in a much terser way, for example by listing all the SELECT columns in one line.
The trick was calling the ->flatMap(...), but there is one catch: As we needed the callback closure to be recursive we had to:
$flatten = function...)use (&$flatten) (note the & before the variable name).Passing it by reference is needed here because we need to reference the callback closure inside itself to call it recursively, but it is still not yet assigned as the closure object is built after the assigment.
Bottom line:
Overall the "complicated" part was just moved from SQL to PHP. But I will concede the Laravel solution brings you much more value, as it is more likely you would reuse those concepts (query scopes, relationships, recursive callbacks) later in future projects.
But I still think the CTE solution has some advantages over the Laravel approach. mainly when your dataset has deeply nested sets. As I said before Laravel would issue lots of queries to get all the child records one level at a time.
CTEs also come handy on other scenarios than traversing hierarchical datasets, I highly recommend you, or anyone, to learn more about them.
Nevertheless, I think learning both techniques will make you a better developer in the long run.
Sorry if the first response was too overwhelming. Hope this one is easier to grasp.
Please or to participate in this conversation.