Whenever possible, I create SQL in stages, not least because it gives me the opportunity to test when I go.
The first thing we need to do is define the top-level categories:
SELECT category_id AS tl_cat_id, category_name AS tl_cat_name, display_order AS tl_disp_order FROM Categories WHERE parent_id = 0;
Now we need to join this with categories and subcategories to get the result:
SELECT t.tl_cat_id, t.cat_name, t.tl_disp_order, c.category_id, c.category_name, CASE WHEN c.parent_id = 0 THEN 0 ELSE c.display_order END AS disp_order FROM Categories AS c JOIN (SELECT category_id AS tl_cat_id, category_name AS tl_cat_name, display_order AS tl_disp_order FROM Categories WHERE parent_id = 0) AS t ON c.tl_cat_id = t.parent_id OR (c.parent_id = 0 AND t.tl_cat_id = c.category_id) ORDER BY tl_disp_order, disp_order;
The join condition is unusual, but should work; it collects rows where the parent identifier matches the current category identifier or rows where the parent identifier is 0, but the category identifier is the same. Then the ordering is almost trivial - except that when you are dealing with a subcategory, you want the parent to be at the top of the list. The CASE expression processes this mapping.
source share