簡體   English   中英

如何使用PHP和MySQL將父子(鄰接)表轉換為嵌套集?

[英]How do you convert a parent-child (adjacency) table to a nested set using PHP and MySQL?

我花了最后幾個小時試圖在線找到這個問題的解決方案。 我已經找到了很多關於如何從嵌套集轉換為鄰接的例子......但很少有相反的方法。 我發現的示例要么不起作用,要么使用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

這是我需要的圖像:

嵌套樹形圖

我有一個函數,基於此論壇帖子的偽代碼( http://www.sitepoint.com/forums/showthread.php?t=320444 ),但它不起作用。 我得到多個具有相同左側值的行。 這不應該發生。

<?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</pre>\n";
                throw new Exception(mysql_error());  
            }
            echo "<p>$s_query</p>\n";

            // get the newly created row id
            $i_new_nested_id = mysql_insert_id();

        }

        return $i_right + 1;
    }



    /**
     * get_source_children
     *
     * Examines the "adjacent" table and finds all the immediate children of a node
     * @param   integer     $i_id           The unique id for a node in the adjacent_table table
     * @return  array                       Returns an array of results or an empty array if no results.
     */
    function get_source_children($i_id)
    {


        $a_return = array();
        $s_query = "SELECT * FROM `adjacent_table` WHERE `father_id` = '".$i_id."'";
        if (!$i_result = mysql_query($s_query))
        {
            echo "<pre>$s_query</pre>\n";
            throw new Exception(mysql_error());  
        }
        if (mysql_num_rows($i_result) > 0)
        {
            while($a = mysql_fetch_assoc($i_result))
            {
                $a_return[] = $a;
            }
        }

        return $a_return;
    }

?>

這是上述腳本的輸出。

INSERT INTO nested_tableidlftrgtcategory )VALUES(NULL,'2','5','Hard Cover')

INSERT INTO nested_tableidlftrgtcategory )VALUES(NULL,'2','7','Large Format')

INSERT INTO nested_tableidlftrgtcategory )VALUES(NULL,'1','8','Books')

INSERT INTO nested_tableidlftrgtcategory )VALUES(NULL,'1','10','CD \\'s')

INSERT INTO nested_tableidlftrgtcategory )VALUES(NULL, nested_table ','13','Vintage')

INSERT INTO nested_tableidlftrgtcategory )VALUES(NULL,'1','14','Magazines')

INSERT INTO nested_tableidlftrgtcategory )VALUES(NULL,'0','15','ROOT')

正如您所看到的,有多個行共享lft值“1”同樣適用於“2”在嵌套集中,左側和右側的值必須是唯一的。 以下是如何在嵌套集中手動編號左右ID的示例:

如何對嵌套集進行編號

圖片來源:Gijs Van Tulder, 參考文章

我在網上找到了答案並更新了此頁面上的問題,以向其他人展示如何完成。

更新 - 解決問題

首先,我錯誤地認為源表(相鄰列表格式的表)需要更改為包含源節點。 不是這種情況。 其次,我通過BING 找到了一個可以解決問題的課程 我已經為PHP5修改了它,並將原作者的mysql相關位轉換為基本PHP。 他正在使用一些DB類。 如果需要,可以稍后將它們轉換為您自己的數據庫抽象類。

顯然,如果您的“源表”具有要移動到嵌套集表的其他列,則必須在下面的類中調整write方法。

希望這將在未來為其他人解決同樣的問題。

<?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</pre>\n";
                throw new Exception(mysql_error());  
            }
            $a_source = array();
            if (mysql_num_rows($i_result))
            {
                $a_source = mysql_fetch_assoc($i_result);
            }

            // root node?  label it unless already labeled in source table
            if (1 == $i_lft && empty($a_source['category']))
            {
                $a_source['category'] = 'ROOT';
            }

            // insert into the new nested tree table
            // use mysql_real_escape_string because one value "CD's"  has a single '
            $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 "<pre>$s_query</pre>\n";
                throw new Exception(mysql_error());  
            }
            else
            {
                // success:  provide feedback
                echo "<p>$s_query</p>\n";
            }
        }
    }

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

    // build a complete copy of the adjacency table in ram
    $s_query = "SELECT `id`,`father_id` FROM `adjacent_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->traverse(0);

?> 

這是輸出:

INSERT INTO nested_tableidlftrgtcategory )VALUES('4','3','4','Hard Cover')

INSERT INTO nested_tableidlftrgtcategory )VALUES('5','5','6','Large Format')

INSERT INTO nested_tableidlftrgtcategory )VALUES('1','2','7','Books')

INSERT INTO nested_tableidlftrgtcategory )VALUES('2','8','9','CD \\'s')

INSERT INTO nested_tableidlftrgtcategory )VALUES('6','11','12','Vintage')

INSERT INTO nested_tableidlftrgtcategory )VALUES('3','10','13','Magazines')

INSERT INTO nested_tableidlftrgtcategory )VALUES('0','1','14','ROOT')

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*)"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM