Дерево смежности с одного стола - PullRequest
3 голосов
/ 14 апреля 2009

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

У меня есть таблица с: id, title, parent_id

И я выделил все записи в массив под названием $ pages.

Тогда используя этот php:

function makeList($pages, $used) {
    if (count($pages)) {
        echo "<ul>";
        foreach ($pages as $page) {
            echo "<li>".$page['pag_title'];
            $par_id = $page['pag_id'];
            $subsql("SELECT * FROM pages WHERE pag_parent = ".$par_id."");

            // running the new sql through an abstraction layer
            $childpages = $dbch->fetchAll();
            makeList($childpages, $used, $lastused);
            echo "</li>";
        }
        echo "</ul>";
    }
}

Такого рода работы, но я получаю повторение любого подменю, например,

  • Начало
    • Новости
      • Sub-новости
    • Статьи
      • Статья
  • Новости
    • Sub-новости
  • Статьи
    • Статья
  • Sub-новости
  • Статья

Я пытался добавить текущий идентификатор в массив, который передается через функцию, а затем с помощью in_array проверить, есть ли он, но у меня не было радости сделать это.

Любая помощь будет принята с благодарностью.

Мне нужно проанализировать все дерево, поэтому выбор родительского элемента в качестве 0 не подходит

Ответы [ 6 ]

3 голосов
/ 16 апреля 2009

Если вы создаете массив страниц, сгруппированных по родительскому идентификатору, рекурсивно составить список довольно просто. Для этого потребуется только один запрос к базе данных.

<?php

 //example data
 $items = array(
    array('id'=>1, 'title'=>'Home', 'parent_id'=>0),
    array('id'=>2, 'title'=>'News', 'parent_id'=>1),
    array('id'=>3, 'title'=>'Sub News', 'parent_id'=>2),
    array('id'=>4, 'title'=>'Articles', 'parent_id'=>0),
    array('id'=>5, 'title'=>'Article', 'parent_id'=>4),
    array('id'=>6, 'title'=>'Article2', 'parent_id'=>4)
 );

 //create new list grouped by parent id
 $itemsByParent = array();
 foreach ($items as $item) {
    if (!isset($itemsByParent[$item['parent_id']])) {
        $itemsByParent[$item['parent_id']] = array();
    }

    $itemsByParent[$item['parent_id']][] = $item;
 }

 //print list recursively 
 function printList($items, $parentId = 0) {
    echo '<ul>';
    foreach ($items[$parentId] as $item) {
        echo '<li>';
        echo $item['title'];
        $curId = $item['id'];
        //if there are children
        if (!empty($items[$curId])) {
            makeList($items, $curId);
        }           
        echo '</li>';
    }
    echo '</ul>';
 }

printList($itemsByParent);
3 голосов
/ 14 апреля 2009

Поскольку он уже выполняет SQL, вам не нужно делать это снаружи перед первым вызовом функции.

function makeList($par_id = 0) {
    //your sql code here
    $subsql("SELECT * FROM pages WHERE pag_parent = $par_id");
    $pages = $dbch->fetchAll();

    if (count($pages)) {
        echo '<ul>';
        foreach ($pages as $page) {
            echo '<li>', $page['pag_title'];
            makeList($page['pag_id']);
            echo '</li>';
        }
        echo '</ul>';
    }
}

Для хранения этого дерева, как вы, возможно, захотите посмотреть на этом сайте: Хранение иерархических данных в базе данных .

1 голос
/ 14 апреля 2009

Откуда берется $ page? Возможно, в вашем коде есть уязвимость, связанная с внедрением sql, если вы не избегаете ее или не используете подготовленный оператор.

Также оператор SELECT внутри цикла for является плохой практикой. Если таблица не такая большая, выберите содержимое всей таблицы, а затем переберите набор результатов в PHP, чтобы построить древовидную структуру данных. Это может занять до n * (n-1) / 2 итераций в патологическом случае, когда ваше дерево является связанным списком. Остановитесь, когда все узлы были добавлены в дерево, или число оставшихся узлов остается неизменным от одной итерации к следующей - это означает, что оставшиеся узлы не являются дочерними элементами вашего корневого узла.

В качестве альтернативы, если ваша база данных поддерживает рекурсивные запросы SQL, вы можете использовать ее, и она будет выбирать только те узлы, которые являются дочерними для вашего родительского узла. Вам все равно придется самостоятельно строить дерево в PHP. Форма запроса будет выглядеть примерно так:

WITH temptable(id, title, parent_id) AS (
  SELECT id, title, parent_id FROM pages WHERE id = ?
  UNION ALL
  SELECT a.id, a.title, a.parent_id FROM pages a, temptable t
   WHERE t.parent_id = a.id
) SELECT * FROM temptable

Заменить '?' во второй строке с идентификатором начальной страницы.

0 голосов
/ 23 января 2014

Поиск старшего родителя, всех родителей и всех потомков узла (улучшения для ответа Тома Хейга):

<?php

 //sample data (can be pulled from mysql)
 $items = array(
    array('id'=>1, 'title'=>'Home', 'parent_id'=>0),
    array('id'=>2, 'title'=>'News', 'parent_id'=>1),
    array('id'=>3, 'title'=>'Sub News', 'parent_id'=>2),
    array('id'=>4, 'title'=>'Articles', 'parent_id'=>0),
    array('id'=>5, 'title'=>'Article', 'parent_id'=>4),
    array('id'=>6, 'title'=>'Article2', 'parent_id'=>4)
 );

 //create new list grouped by parent id
 $itemsByParent = array();
 foreach ($items as $item) {
    if (!isset($itemsByParent[$item['parent_id']])) {
        $itemsByParent[$item['parent_id']] = array();
    }

    $itemsByParent[$item['parent_id']][] = $item;
 }

 //print list recursively 
 function printList($items, $parentId = 0) {
    echo '<ul>';
    foreach ($items[$parentId] as $item) {
        echo '<li>';
        echo $item['title'];
        $curId = $item['id'];
        //if there are children
        if (!empty($items[$curId])) {
            printList($items, $curId);
        }           
        echo '</li>';
    }
    echo '</ul>';
 }

printList($itemsByParent);


/***************Extra Functionality 1****************/

function findTopParent($id,$ibp){


    foreach($ibp as $parentID=>$children){ 

            foreach($children as $child){


            if($child['id']==$id){


             if($child['parent_id']!=0){

            //echo $child['parent_id'];
            return findTopParent($child['parent_id'],$ibp);

          }else{ return $child['title'];}

         }              
        }
}
}

$itemID=7;  
$TopParent= findTopParent($itemID,$itemsByParent);





/***************Extra Functionality 2****************/

function getAllParents($id,$ibp){ //full path

foreach($ibp as $parentID=>$nodes){ 

    foreach($nodes as $node){

        if($node['id']==$id){

             if($node['parent_id']!=0){

                $a=getAllParents($node['parent_id'],$ibp);
                array_push($a,$node['parent_id']);
                return $a;

              }else{
                    return array();
                  }

             }
    }
}
}


$FullPath= getAllParents(3,$itemsByParent);
print_r($FullPath);

/*
Array
(
[0] => 1
[1] => 2
)
*/

/***************Extra Functionality 3****************/

 //this function gets all offspring(subnodes); children, grand children, etc...
 function getAllDescendancy($id,$ibp){

 if(array_key_exists($id,$ibp)){

         $kids=array();
         foreach($ibp[$id] as $child){

            array_push($kids,$child['id']);

            if(array_key_exists($child['id'],$ibp))

$kids=array_merge($kids,getAllDescendancy($child['id'],$ibp));

             }

         return $kids;       

     }else{
            return array();//supplied $id has no kids
          }
 }

print_r(getAllDescendancy(1,$itemsByParent));
/*
Array
(
[0] => 2
[1] => 3
)
*/


print_r(getAllDescendancy(4,$itemsByParent));
/*
Array
(
[0] => 5
[1] => 6
)
*/


print_r(getAllDescendancy(0,$itemsByParent));
/*
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
)

*/

?>
0 голосов
/ 16 апреля 2009

Когда эта таблица становится большой, рекурсия может стать громоздкой. Я написал сообщение в блоге о методе без рекурсии: http://www.alandelevie.com/2008/07/12/recursion-less-storage-of-hierarchical-data-in-a-relational-database/

0 голосов
/ 14 апреля 2009

Самое простое исправление будет просто, когда вы делаете начальный выбор для установки $pages (который вы не показываете), добавьте предложение WHERE как:

WHERE pag_parent = 0

(или IS NULL, в зависимости от того, как вы храните страницы «верхнего уровня»).

Таким образом, изначально вы не выберете всех детей.

...