It might be worth doing this in PHP, as opposed to SQL if you're working with an external database. I haven't benchmarked the following, so try with your data and see if performance is problematic or not.
You can choose yourself what to do with orphaned records (which reference parentIDs that don't exist anymore).
Ordering in PHP like this requires that you have all of your data beforehand, so use something like PDO's fetchAll(PDO::FETCH_ASSOC)
method, which should result in something like this:
$data_from_database = array(
array("id" => 1, "parentId" => 0, "description" => "Level 1"),
array("id" => 2, "parentId" => 1, "description" => "Level 1a"),
array("id" => 3, "parentId" => 1, "description" => "Level 1b"),
array("id" => 4, "parentId" => 0, "description" => "Level 2"),
array("id" => 5, "parentId" => 2, "description" => "Level 1a1"),
array("id" => 6, "parentId" => 5, "description" => "Level 1a11a"),
array("id" => 7, "parentId" => 5, "description" => "Level 1a11b"),
array("id" => 8, "parentId" => 9, "description" => "Level 3"),
);
First off, you'll want to have the primary key (ID) as the array's keys. The following also adds the keys "children" and "is_orphan" to every record.
$data_by_id = array();
foreach($data_from_database as $row)
$data_by_id[$row["id"]] = $row + array(
"children" => array(),
"is_orphan" => false
);
This will look something like this:
$data_from_database = array(
1 => array("id" => 1, "parentId" => 0, "description" => "Level 1",
"children" => array(), "is_orphan" => false),
...
);
Now, it gets tricky: we'll loop through the array and add references.
foreach($data_by_id as &$row)
{
if($row["parentId"] > 0)
{
if(isset($data_by_id[$row["parentId"]]))
$data_by_id[$row["parentId"]]["children"][] = &$row;
else
$row["is_orphan"] = true;
}
}
unset($row); // Clear reference (important).
The last step is to clean up the 'root' of the array. It'll contain references to duplicate rows.
foreach($data_by_id as $id => $row)
{
// If you use this option, you'll remove
// orphaned records.
#if($row["parentId"] > 0)
# unset($data_by_id[$id]);
// Use this to keep orphans:
if($row["parentId"] > 0 AND !$row["is_orphan"])
unset($data_by_id[$id]);
}
Use print_r($data_by_id)
after every step to see what happens.
If this proves to be a time consuming operation, try to build up the tree by only doing SELECT id, parentId FROM ...
and then later fetching the metadata such as description
. You could also store the result in Memcache or serialized into a database.