Как преобразовать таблицу parent-child (смежность) во вложенный набор с использованием PHP и MySQL? - PullRequest
21 голосов
/ 12 января 2011

Последние несколько часов я пытался найти решение этого вопроса в Интернете. Я нашел множество примеров того, как преобразовать из вложенного множества в смежность ... но немногие из них идут наоборот. Примеры, которые я нашел, либо не работают, либо используют процедуры MySQL. К сожалению, я не могу использовать процедуры для этого проекта. Мне нужно чистое решение PHP.

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

id          parent_id         category
1           0                 Books
2           0                 CD's
3           0                 Magazines
4           1                 Books/Hardcover
5           1                 Books/Large Format
6           3                 Magazines/Vintage

И я хотел бы преобразовать его в таблицу вложенных наборов ниже:

id    left    right          category
0     1       14             Root Node
1     2       7              Books
4     3       4              Books/Hardcover
5     5       6              Books/Large Format
2     8       9              CD's
3     10      13             Magazines
6     11      12             Magazines/Vintage

Вот изображение того, что мне нужно:

Nested Tree Chart

У меня есть функция, основанная на псевдокоде из этого сообщения на форуме (http://www.sitepoint.com/forums/showthread.php?t=320444), но она не работает. Я получаю несколько строк с одинаковым значением left. Этого не должно быть.

<code><?php

/**

--
-- Table structure for table `adjacent_table`
--

CREATE TABLE IF NOT EXISTS `adjacent_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `father_id` int(11) DEFAULT NULL,
  `category` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=8 ;

--
-- Dumping data for table `adjacent_table`
--

INSERT INTO `adjacent_table` (`id`, `father_id`, `category`) VALUES
(1, 0, 'ROOT'),
(2, 1, 'Books'),
(3, 1, 'CD''s'),
(4, 1, 'Magazines'),
(5, 2, 'Hard Cover'),
(6, 2, 'Large Format'),
(7, 4, 'Vintage');

--
-- Table structure for table `nested_table`
--

CREATE TABLE IF NOT EXISTS `nested_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lft` int(11) DEFAULT NULL,
  `rgt` int(11) DEFAULT NULL,
  `category` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

*/

    mysql_connect('localhost','USER','PASSWORD') or die(mysql_error());
    mysql_select_db('DATABASE') or die(mysql_error());
    adjacent_to_nested(0);

    /**
     * adjacent_to_nested
     *
     * Reads a "adjacent model" table and converts it to a "Nested Set" table.
     * @param   integer     $i_id           Should be the id of the "root node" in the adjacent table;
     * @param   integer     $i_left         Should only be used on recursive calls.  Holds the current value for lft
     */
    function adjacent_to_nested($i_id, $i_left = 0)
    {

        // the right value of this node is the left value + 1
        $i_right = $i_left + 1;

        // get all children of this node
        $a_children = get_source_children($i_id);
        foreach  ($a_children as $a)
        {

            // recursive execution of this function for each child of this node
            // $i_right is the current right value, which is incremented by the 
            // import_from_dc_link_category method
            $i_right = adjacent_to_nested($a['id'], $i_right);

            // insert stuff into the our new "Nested Sets" table
            $s_query = "
                INSERT INTO `nested_table` (`id`, `lft`, `rgt`, `category`) 
                VALUES(
                    NULL, 
                    '".$i_left."',
                    '".$i_right."',
                    '".mysql_real_escape_string($a['category'])."'
                )
            ";
            if (!mysql_query($s_query))
            {
                echo "<pre>$s_query
\ п "; бросить новое исключение (mysql_error ()); } echo "

$ s_query

\ n"; // получаем новый идентификатор строки $ i_new_nested_id = mysql_insert_id (); } вернуть $ i_right + 1; } / ** * get_source_children * * Исследует "соседнюю" таблицу и находит всех непосредственных потомков узла * @param integer $ i_id Уникальный идентификатор узла в таблице смежных таблиц * @return array Возвращает массив результатов или пустой массив, если результатов нет. * / функция get_source_children ($ i_id) { $ a_return = array (); $ s_query = "SELECT * FROM` adj_table` WHERE `Father_id` = '". $ i_id. "'"; if (! $ i_result = mysql_query ($ s_query)) { echo "
$s_query
\ n"; бросить новое исключение (mysql_error ()); } if (mysql_num_rows ($ i_result)> 0) { while ($ a = mysql_fetch_assoc ($ i_result)) { $ a_return [] = $ a; } } вернуть $ a_return; } ?>

Это вывод вышеуказанного скрипта.

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ( NULL, '2', '5', 'Hard Cover')

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ( NULL, «2», «7», «Большой формат»)

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ( NULL, «1», «8», «Книги»)

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ( NULL, '1', '10', 'CD \' s ')

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ( NULL, '10', '13', 'Vintage')

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ( NULL, «1», «14», «Журналы»)

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ( NULL, '0', '15', 'ROOT')

Как видите, есть несколько строк, разделяющих значение lft в «1», то же самое относится и к «2». Во вложенном множестве значения для left и right должны быть уникальными. Вот пример того, как вручную нумеровать левый и правый идентификаторы во вложенном наборе:

How to number nested sets

Изображение предоставлено: Gijs Van Tulder, ссылка статья

Ответы [ 2 ]

12 голосов
/ 12 января 2011

Я нашел ответ в Интернете и обновил вопрос на этой странице, чтобы показать другим, как это делается.

ОБНОВЛЕНИЕ - РЕШЕНИЕ ПРОБЛЕМЫ

Во-первых, я ошибочно полагал, что исходная таблица(тот, что в формате смежных списков) необходимо изменить, чтобы включить узел источника.Это не вариант.Во-вторых, я нашел класс через BING, который добился цели.Я изменил его для PHP5 и преобразовал биты, связанные с MySQL оригинального автора, в базовый PHP.Он использовал какой-то класс БД.Вы можете преобразовать их в свой собственный класс абстракции базы данных позже, если хотите.

Очевидно, что если в вашей «исходной таблице» есть другие столбцы, которые вы хотите переместить в таблицу вложенных множеств, вам придется настроить записьметод в классе ниже.

Надеемся, что это спасет кого-то еще от подобных проблем в будущем.

<code><?php

/**


--
-- Table structure for table `adjacent_table`
--

DROP TABLE IF EXISTS `adjacent_table`;
CREATE TABLE IF NOT EXISTS `adjacent_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `father_id` int(11) DEFAULT NULL,
  `category` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=8 ;

--
-- Dumping data for table `adjacent_table`
--

INSERT INTO `adjacent_table` (`id`, `father_id`, `category`) VALUES
(1, 0, 'Books'),
(2, 0, 'CD''s'),
(3, 0, 'Magazines'),
(4, 1, 'Hard Cover'),
(5, 1, 'Large Format'),
(6, 3, 'Vintage');

--
-- Table structure for table `nested_table`
--

DROP TABLE IF EXISTS `nested_table`;
CREATE TABLE IF NOT EXISTS `nested_table` (
  `lft` int(11) NOT NULL DEFAULT '0',
  `rgt` int(11) DEFAULT NULL,
  `id` int(11) DEFAULT NULL,
  `category` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`lft`),
  UNIQUE KEY `id` (`id`),
  UNIQUE KEY `rgt` (`rgt`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

*/

    /**
     * @class   tree_transformer
     * @author  Paul Houle, Matthew Toledo
     * @created 2008-11-04
     * @url     http://gen5.info/q/2008/11/04/nested-sets-php-verb-objects-and-noun-objects/
     */
    class tree_transformer 
    {

        private $i_count;
        private $a_link;

        public function __construct($a_link) 
        {
            if(!is_array($a_link)) throw new Exception("First parameter should be an array. Instead, it was type '".gettype($a_link)."'");
            $this->i_count = 1;
            $this->a_link= $a_link;
        }

        public function traverse($i_id) 
        {
            $i_lft = $this->i_count;
            $this->i_count++;

            $a_kid = $this->get_children($i_id);
            if ($a_kid) 
            {
                foreach($a_kid as $a_child) 
                {
                    $this->traverse($a_child);
                }
            }
            $i_rgt=$this->i_count;
            $this->i_count++;
            $this->write($i_lft,$i_rgt,$i_id);
        }   

        private function get_children($i_id) 
        {
            return $this->a_link[$i_id];
        }

        private function write($i_lft,$i_rgt,$i_id) 
        {

            // fetch the source column
            $s_query = "SELECT * FROM `adjacent_table` WHERE `id`  = '".$i_id."'";
            if (!$i_result = mysql_query($s_query))
            {
                echo "<pre>$s_query
\ n "; выбросит новое исключение (mysql_error ());} $ a_source = array(); if (mysql_num_rows ($ i_result)) {$ a_source = mysql_fetch_assoc ($ i_result);} // корневой узел? пометьте его, если он уже не помечен в исходной таблице, если (1 == $ i_lft && пусто ($ a_source ['category)'])) {$ a_source [' category '] =' ROOT ';} // вставить в новую таблицу вложенного дерева // использовать mysql_real_escape_string, потому что одно значение "CD" имеет единственный' $ s_query = "INSERT INTO` nested_table`(`id`,` lft`, `rgt`,` category`) VALUES ('". $ i_id."', '". $ i_lft."', '". $ i_rgt."', '".mysql_real_escape_string($ a_source ['category']). "')"; if (! $ i_result = mysql_query ($ s_query)) {echo "
$s_query
\ n"; выбросить новыйИсключение (mysql_error ());} else {// success: обеспечить обратную связь echo "

$ s_query

\ n";}}} mysql_connect ('localhost', 'USER', 'PASSWORD') или die (mysql_error ());mysql_select_db ('DATABASE') или die (mysql_error ());// создаем полную копию таблицы смежности в ram $ s_query = "SELECT` id`, `Father_id` FROM `acent_table`";$ i_result = mysql_query ($ s_query);$ a_rows = array ();while ($ a_rows [] = mysql_fetch_assoc ($ i_result));$ a_link = array ();foreach ($ a_rows as $ a_row) {$ i_father_id = $ a_row ['Father_id'];$ i_child_id = $ a_row ['id'];if (! array_key_exists ($ i_father_id, $ a_link)) {$ a_link [$ i_father_id] = array ();} $ a_link [$ i_father_id] [] = $ i_child_id;} $ o_tree_transformer = new tree_transformer ($ a_link);$ O_tree_transformer-> траверсы (0);?>

Вот вывод:

INSERT INTO nested_table (id, lft, rgt, category) VALUES ('4 ',' 3 ',' 4 ',' Hard Cover ')

INSERT INTO nested_table (id, lft, rgt, category) VALUES (' 5 ','5', '6', 'Большой формат')

INSERT INTO nested_table (id, lft, rgt, category) VALUES ('1', '2', '7', 'Книги')

INSERT INTO nested_table (id, lft, rgt, category) VALUES ('2', '8', '9', 'CD \' s ')

INSERT INTO nested_table (id, lft, rgt, category) VALUES (' 6 ',' 11 ',' 12 ','Винтаж')

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ («3», «10», «13», «Журналы»)

INSERT INTO nested_table (id, lft, rgt, category) ЗНАЧЕНИЯ ('0', '1', '14', 'ROOT')

0 голосов
/ 19 мая 2015

Преобразование Bash:

# SQL command to fetch necessary fields, output it to text archive "tree"
SELECT id, parent_id, name FROM projects;

# Make a list "id|parentid|name" and sort by name
cat tree |
  cut -d "|" -f 2-4 |
  sed 's/^ *//;s/ *| */|/g' |
  sort -t "|" -k 3,3 > list

# Creates the parenthood chain on second field
while IFS="|" read i p o
do
  l=$p
  while [[ "$p" != "NULL" ]]
  do
    p=$(grep -w "^$p" list | cut -d "|" -f 2)
    l="$l,$p"
  done
  echo "$i|$l|$o"
done < list > listpar

# Creates left and right on 4th and 5th fields for interaction 0
let left=0
while IFS="|" read i l o
do
  let dif=$(grep "\b$i,NULL|" listpar | wc -l)*2+1
  let right=++left+dif
  echo "$i|$l|$o|$left|$right"
  let left=right
done <<< "$(grep "|NULL|" listpar)" > i0

# The same for following interactions
n=0
while [ -s i$n ]
do
  while IFS="|" read i l nil left nil
  do
    grep "|$i,$l|" listpar |
    while IFS="|" read i l o
    do
      let dif=$(grep "\b$i,$l|" listpar | wc -l)*2+1
      let right=++left+dif
      echo "$i|$l|$o|$left|$right"
      let left=right
    done
  done < i$n > i$((++n))
done

# Show concatenated
cat i*|sort -t"|" -k 4n

# SQL commands
while IFS="|" read id nil nil left right
do
  echo "UPDATE projects SET lft=$left, rgt=$right WHERE id=$id;"
done <<< "$(cat i*)"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...