Тут есть несколько варианта:
1) Если у тебя есть только категория+саб-категория, то выбираешь главные и в момент их отображения выбираешь вложенные, псевдокод
$items = SELECT * FROM table WHERE parent_id=0
foreach($items as $item)
{
echo $item->name
$subitems = SELECT * FROM table WHERE parent_id={$item->id}
foreach ($subitems as $subitem) {
echo $subitem->name
}
}
2) другой вариант, это выбрать все категории и сортировать их во вложенные массивы и потом вывести, что-то вроде
$items = SELECT * FROM table
$menu = [];
foreach($items as $item) {
if ($item->parent_id == 0) {
$menu[$item->id]['parent'] = $item;
} else {
$menu[$item->parent_id]['items'][] = $item;
}
}
foreach ($menu as $item) {
echo $item['parent']->name;
if (!empty($item['items'])) {
foreach ($item['items'] as $subitem) {
echo $subitem->name;
}
}
}
3) Это та же разновидность первого и второго вариантов, но с обеспечением бесконечного уровня вложенностей. В этом случае обычно используется рекурсия, что-то вроде
$printer = function printNode($items) {
foreach($items as $item) {
echo $item->name
$subitems = SELECT * FROM table WHERE parent_id={$item->id}
if ($subitems) {
printNode($subitems);
}
}
}
$items = SELECT * FROM table WHERE parent_id=0
printNode($items)