drzewo na podstawie parent (ip).
np.
Standardowe by było
id(1) parent(0) position(1) PC
id(2) parent(1.2) position(2) Laptopy
id(3) parent(1.2) position(3) Standardowe
id(4) parent(1.2) position(3) Niestandardowe.
Postion tylko dałem bo pamiętam że później łatwiej query budować. Position oznacza miejscu ID danej kategorii w parent.
SELECT * FROM `bug_categories` WHERE CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(`parent`, '.', :position), '.', -1) AS UNSIGNED) = :catId
Pobiera listę subkategorii dla aktualnej kategorii. Później tylko przy wyświetlaniu musisz sprawdzić czy ostatnia pozycja w parent jest równa aktualnej pozycji - wtedy dajesz do wyświetlenia. Dużo bajerów można tak robić, jest szybsze o wiele od rekurencyjnej strukturze.
Ogólnie będziesz musiał pomyśleć co i jak, jak załapiesz o co chodzi sam zrozumiesz jak napisać wszystko.

Dla przykładu ale już nie będę opisywał całego działania kodu ( a całej klasy Ci nie dam):
$parent = explode('.', $row['parent']); $parent_last = count($parent) - 1; if($parent[$parent_last] == $catId)
{
$catSub[$row['id']] = $row;
$catSub[$row['id']]['numSubCat'] = 0;
$catSub[$row['id']]['tickets'] = $ticket_count;
$catSub[$row['id']]['tickets_p'] = $ticket_p;
}else{
$cat[$id]['numSubCat']++;
$cat[$id]['tickets'] += $ticket_count;
$cat[$id]['tickets_p'] += $ticket_p;
if(isset($catSub[$parent[$row['position']-2
]])) {
$catSub[$parent[$row['position']-2]]['numSubCat']++;
$catSub[$parent[$row['position']-2]]['tickets'] += $ticket_count;
$catSub[$parent[$row['position']-2]]['tickets_p'] += $ticket_p;
}
}