Запрос MySQL для заказа родителями, затем дочерние места

У меня есть таблица страниц в моей базе данных, каждая страница может иметь родителя, как показано ниже:

id parent_id title 1 0 Home 2 0 Sitemap 3 0 Products 4 3 Product 1 5 3 Product 2 6 4 Product 1 Review Page 

Какой был бы лучший запрос MySQL для выбора всех страниц, заказанных родителем, тогда потом ребенок, а потом еще раз, если будет более одного уровня, будет максимум три уровня. Вышеприведенный пример привел бы к желаемому порядку:

 Home Sitemap Products Product 1 Product 1 Review Page Product 2 

Я думаю, вы должны поместить еще одно поле в свою таблицу, называемое уровнем и сохранить в нем уровень узла, а затем отсортировать свой запрос по уровню, а затем по родительскому.

Если вам нужно придерживаться своей модели, я предлагаю этот запрос:

 SELECT p.id, p.title, ( SELECT LPAD(parent.id, 5, '0') FROM page parent WHERE parent.id = p.id AND parent.parent_id = 0 UNION SELECT CONCAT(LPAD(parent.id, 5, '0'), '.', LPAD(child.id, 5, '0')) FROM page parent INNER JOIN page child ON (parent.id = child.parent_id) WHERE child.id = p.id AND parent.parent_id = 0 UNION SELECT CONCAT(LPAD(parent.id, 5, '0'), '.', LPAD(child.id, 5, '0'), '.', LPAD(grandchild.id, 5, '0')) FROM page parent INNER JOIN page child ON (parent.id = child.parent_id) INNER JOIN page grandchild ON (child.id = grandchild.parent_id) WHERE grandchild.id = p.id AND parent.parent_id = 0 ) AS level FROM page p ORDER BY level; 

Пример набора результатов:

 +-----+-------------------------+-------------------+ | id | title | level | +-----+-------------------------+-------------------+ | 1 | Home | 00001 | | 2 | Sitemap | 00002 | | 3 | Products | 00003 | | 4 | Product 1 | 00003.00004 | | 6 | Product 1 Review Page 1 | 00003.00004.00006 | | 646 | Product 1 Review Page 2 | 00003.00004.00646 | | 5 | Product 2 | 00003.00005 | | 644 | Product 3 | 00003.00644 | | 645 | Product 4 | 00003.00645 | +-----+-------------------------+-------------------+ 9 rows in set (0.01 sec) 

Выход EXPLAIN:

 +------+--------------------+--------------+--------+---------------+---------+---------+--------------------------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+--------------------+--------------+--------+---------------+---------+---------+--------------------------+------+----------------+ | 1 | PRIMARY | p | ALL | NULL | NULL | NULL | NULL | 441 | Using filesort | | 2 | DEPENDENT SUBQUERY | parent | eq_ref | PRIMARY,idx1 | PRIMARY | 4 | tmp.p.id | 1 | Using where | | 3 | DEPENDENT UNION | child | eq_ref | PRIMARY,idx1 | PRIMARY | 4 | tmp.p.id | 1 | | | 3 | DEPENDENT UNION | parent | eq_ref | PRIMARY,idx1 | PRIMARY | 4 | tmp.child.parent_id | 1 | Using where | | 4 | DEPENDENT UNION | grandchild | eq_ref | PRIMARY,idx1 | PRIMARY | 4 | tmp.p.id | 1 | | | 4 | DEPENDENT UNION | child | eq_ref | PRIMARY,idx1 | PRIMARY | 4 | tmp.grandchild.parent_id | 1 | | | 4 | DEPENDENT UNION | parent | eq_ref | PRIMARY,idx1 | PRIMARY | 4 | tmp.child.parent_id | 1 | Using where | | NULL | UNION RESULT | <union2,3,4> | ALL | NULL | NULL | NULL | NULL | NULL | | +------+--------------------+--------------+--------+---------------+---------+---------+--------------------------+------+----------------+ 8 rows in set (0.00 sec) 

Я использовал эту таблицу:

 CREATE TABLE `page` ( `id` int(11) NOT NULL, `parent_id` int(11) NOT NULL, `title` varchar(255) default NULL, PRIMARY KEY (`id`), KEY `idx1` (`parent_id`) ); 

Обратите внимание, что я включил индекс в parent_id для повышения производительности.

Если у вас есть некоторый контроль над схемой таблицы, вы можете захотеть использовать вместо этого вложенное представление набора. Майк Хиллер написал статью об этом:

Управление иерархическими данными в MySQL

Работай умом, а не силой:

 SELECT menu_name , CONCAT_WS('_', level3, level2, level1) as level FROM (SELECT t1.menu_name as menu_name , t3.sorting AS level3, t2.sorting AS level2, t1.sorting AS level1 FROM en_menu_items as t1 LEFT JOIN en_menu_items as t2 on t1.parent_id = t2.id LEFT JOIN en_menu_items as t3 on t2.parent_id = t3.id ) as depth_table ORDER BY level 

то есть ..

Тьфу. Такие запросы, связанные с деревьями, являются раздражающими и обычно, если вы хотите, чтобы он был масштабируемым для любого количества уровней, вы не будете делать это с помощью одного запроса, вы будете использовать несколько, создающих дерево на каждом уровне.

Ну, вы всегда можете получить все это в одном запросе и обработать его на PHP. Вероятно, это был бы более простой способ получить дерево.