SQL Query and PHP manipulation with Nested Set Model - php

I was reading this article, http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/.
I wanted to give a simple example and then ask you how do I get the desired result? So here is the example:
+---------+-----------------------------+
| product_id | product_name |
+---------+-----------------------------+
| 1 | Example Product |
+---------+-----------------------------+
+---------+-----------------------------+
| product_id | category_id |
+---------+-----------------------------+
| 1 | 2 |
| 1 | 4 |
+---------+-----------------------------+
+-------------+--------------------+------+------+
| category_id | name | lft | rgt |
+-------------+--------------------+------+------+
| 1 | Electronics | 1 | 8 |
| 2 | Televisions | 2 | 3 |
| 3 | Portable Electronics | 4 | 7 |
| 4 | CD Players | 5 | 6 |
+-------------+--------------------+------+------+
I want to be able to display the following result in HTML after querying and then manipulating the data in PHP:
"Example Product" Categories:
Electronics
Televisions
Portable Electronics
CD Players
Can you help walk me through the query and manipulation in PHP to achieve this result?
Some specifics to think about:
Notice how both categories are under Electronics, but "Electronics" appears only once here, displaying each of the subcategories it belongs to below
The result should eventually be a PHP multi-dimensional array with the category containing an array of sub-categories and each sub-category containing an array of sub-subcategories if they exist.
I imagine printing the depth will be very important for constructing the right tree in HTML.

I thought this was a nice challenge .. here's my solution:
Basically: read a node, then all following nodes with a rgt smaller than your rgt are your children, do this recursively.
I've used a peek/consume to read from mysql like you normally would.
The script will break or loop if the query gives no results, or if the data-set is broken.
class NestedNodeReader {
private $mysql_result;
private $peeked = false;
private $last_peek;
public function __construct($mysql_result) {
$this->mysql_result = $mysql_result;
}
public function getTree() {
$root = $this->consume();
$root["children"] = $this->getSubTree($root["rgt"]);
return $root;
}
private function getSubTree($stop_at) {
$nodes = array();
$node = $this->peek();
while ($node["rgt"] < $stop_at) {
$node = $this->consume();
$node["children"] = $this->getSubTree($node["rgt"]);
$nodes[] = $node;
$node = $this->peek();
if (false === $node) {
break;
}
}
return $nodes;
}
private function peek() {
if (false === $this->peeked) {
$this->peeked = true;
$this->last_peek = mysql_fetch_assoc($this->mysql_result);
}
return $this->last_peek;
}
private function consume() {
if (false === $this->peeked) {
return mysql_fetch_assoc($this->mysql_result);
} else {
$this->peeked = false;
return $this->last_peek;
}
}
}
$query = "SELECT node.name, node.lft, node.rgt
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;"
$mysql_result = mysql_query($query);
$nnr = new NestedNodeReader($mysql_result);
print_r($nnr->getTree());

Related

How to create hierarchial parent cildren in recursive data with Codeigniter

I want to make some like threaded data, so the data is showing with tree-view, but there is a problem, the data is not showing correctly, can you give me some suggest?
Here my table
+-------------+--------------------+----------------------------------+
| division_id | division_parent_id | division_name |
+-------------+--------------------+----------------------------------+
| 1 | 0 | division one |
+-------------+--------------------+----------------------------------+
| 2 | 1 | division one child one |
+-------------+--------------------+----------------------------------+
| 3 | 1 | division one child two |
+-------------+--------------------+----------------------------------+
| 4 | 2 | division grandchild one-one |
+-------------+--------------------+----------------------------------+
| 5 | 2 | division grandchild one-two |
+-------------+--------------------+----------------------------------+
| 6 | 3 | division grandchild two-one |
+-------------+--------------------+----------------------------------+
| 7 | 3 | division grandchild two-two |
+-------------+--------------------+----------------------------------+
| 8 | 4 | division greatgradchild one-one |
+-------------+--------------------+----------------------------------+
| 9 | 4 | division greatgrandchild one-two |
+-------------+--------------------+----------------------------------+
and my code
function getThreaded($row){
$result = "<ul>";
$result .= "<li>".$row['division_name']."</li>";
$q = $this->db->get_where('tb_m_division', array('division_parent_id' => $row['division_id']));
if($q->num_rows() > 0 )
{
foreach($q->result_array() as $row)
{
$result .= $this->getThreaded($row);
}
}
$result .="</ul>";
return $result;
}
function prepare(){
$q = $this->db->get_where('tb_m_division', array('division_parent_id' => '0'));
foreach($q->result_array() as $row)
{
$result = $this->getThreaded($row);
}
// $this->load->view('header');
// $this->load->view('v_tree', $data);
// $this->load->view('footer');
echo $result;
}
the data only displaying the last inputed, if I add the new data then the another data is not displaying.
You need a recursive function. Something like:
function getDivisions($parent_id = 0)
{
$result = '';
$this->db->where('division_parent_id', $parent_id);
$query = $this->db->get('tb_m_division', 0);
if ($query->num_rows() > 0)
{
$rows = $result_array();
$result = "<ul>";
foreach($rows as $row)
{
$result .= "<li>".$row['division_name']."</li>";
// get children
$result .= $this->getDivision($row['division_id']);
$result .= "</ul>";
}
}
return $result;
}
I have not tested that code, so it might be buggy, but the idea is that the function calls itself to get children of each row.
Hope that helps,
Paul
PS If you need to get different classes onto the children just send a level counter through to the function too, so level=1 is top level, level=2 is children, level=3 is grandchildren. Then alter the li line depending on the level set.

Find insert position and number of childs in a binary tree using php mysql

below is my table data
+-------------+-----------+----------------+
| customer_id | parent_id | node_direction |
+-------------+-----------+----------------+
| 1 | 0 | T |
| 2 | 1 | L |
| 3 | 1 | R |
| 4 | 2 | L |
| 5 | 2 | R |
| 6 | 4 | L |
+-------------+-----------+----------------+
Which represents the following tree
1
|
---------
| |
2 3
|
-------
| |
4 5
|
-----
|
6
I need to find the position for insertion by parent id
For Example:
1) if parent id is 1 then insert position will be root-3 position-L
2) if parent_id is 2 then insert position will be root-4 position-R
3) if parent_id is 3 then insert position will be root-3 position-L
The thing is it need to follow the binary structure
I also need to have count of sub nodes by parent node for example:
1 - 5
2 - 3
3 - 0
4 - 1
5 - 0
I need to accomplish this in php and mysql.
Can anyone suggest to me the easiest way to achieve this?
function getNodeInsertPostionByParentId($parentId){
$position = array('status'=>false);
$parents = array();
foreach($parentId as $parent){
$qry = "select customer_id,node_direction from mlm_nodes where parent_id =".$parent." order by node_direction";
$rst = mysql_query($qry);
$count = mysql_num_rows($rst);
if($count==2){
while($row = mysql_fetch_assoc($rst)){
$parents[$parent][] = $row['customer_id'];
}
}elseif($count==1){
$position['status'] = true;
$position['parentId'] = $parent;
$position['node'] = 'R';
//echo '<pre>1';print_r($position);echo '</pre>';
return $position;
}else{
$position['status'] = true;
$position['parentId'] = $parent;
$position['node'] = 'L';
//echo '<pre>2';print_r($position);echo '</pre>';
return $position;
}
}
return $this->searchByParents($parents);
}
function searchByParents($parents){
foreach($parents as $parent){
return $this->getNodeInsertPostionByParentId($parent);
}
}
echo '<pre>';print_r($this->getNodeInsertPostionByParentId(array('4')));die;
This works as expected for finding node position by parent id

Build a Category tree with a php recursion

I have a MySQL table with categories whic look like:
categoryID | categoryName | parentID
------------------------------------
1 | books | NULL
------------------------------------
2 | newspapers | NULL
------------------------------------
3 | sci-fi | 1
------------------------------------
4 | robot stories | 3
-------------------------------------
etc.
I need to build a category tree with recursion when I have only the ID of 'robot stories' and it has to look like:
books -> sci-fi -> robot stories
Any advise will be helpful!
If the tree is not too big, I would just load the whole thing in memory (you can cache it too):
$tree = [];
$stmt = $db->query("SELECT * FROM categories");
while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
$tree[$row['categoryID']] = $row;
}
$currentCategoryId = 4;
$hierarchy = [];
// build hierarchy, working from leaf to root
while (isset($tree[$currentCategoryId])) {
array_unshift($hierarchy, $tree[$currentCategoryId]);
$currentCategoryId = $tree[$currentCategoryId]['parentId'];
}
print_r($hierarchy);

mysql + php retrieve leaf children with path

I have a table that like this.
+-----------+-----------+-----------+-----------+-----------+-----------+
|id | parent_id | name | order | status | date_add |
+-----------+-----------+-----------+-----------+-----------+-----------+
|1 | 0 | shoes | 1 | 1 | 2011-04-02|
+-----------+-----------+-----------+-----------+-----------+-----------+
|2 | 1 | male | 2 | 1 | 2011-04-02|
+-----------+-----------+-----------+-----------+-----------+-----------+
|3 | 1 | female | 3 | 1 | 2011-04-02|
+-----------+-----------+-----------+-----------+-----------+-----------+
|4 | 3 | red shoes | 4 | 1 | 2011-04-02|
+-----------+-----------+-----------+-----------+-----------+-----------+
I want to select only the leaf children, with their path.
I want to come to the result as follows:
+------+-------------------------------------+
| 2 | shoes/male |
+------+-------------------------------------+
| 4 | shoes/female/red shoes |
+------+-------------------------------------+
if this does not only sql can also be php + sql
Please help me.
Very simple solution to print out id and path to all last child nodes using PHP as I am not aware of a way of doing this within MySQL. Hope this helps!
function getChildren($parent= "", $x = 0) {
$sql = "SELECT id, name FROM recurr WHERE parentId = $x";
$rs = mysql_query($sql);
//echo "Name: $parent has ". mysql_num_rows($rs)." children<br/>";
while ($obj = mysql_fetch_object($rs)) {
if (hasChildren($obj->id)) {
getChildren($parent."/".$obj->name, $obj->id);
} else {
echo $obj->id .", ".$parent."/".$obj->name."<br/>";
}
}
}
function hasChildren($x) {
$sql = "SELECT * FROM recurr WHERE parentId = $x";
$rs = mysql_query($sql);
if (mysql_num_rows($rs) > 0) {
return true;
} else {
return false;
}
}
To run just call:
getChildren();
I highly recommend implementing nested set model for hierarchical MySQL data. Here is the link for more info: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/. In the link, you will also find the answer to your question (under "The Adjacency List Model", "Finding all the Leaf Nodes" section)

Category Hierarchy (PHP/MySQL)

I am trying to get my all categories and sub-categories from MySQL database in a hierarchy:
My result should be like that (just example):
Cat A
Sub-Cat 1
Sub_Sub_Cat 1
Sub_Sub_Cat 2
Sub_Cat 2
Cat B
Cat C
...
MySQL code:
CREATE TABLE IF NOT EXISTS `categories` (
`category_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`parent_id` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT 'for sub-categories'
PRIMARY KEY (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
Simply, how can get it in a hirarchy with PHP codes?
When using an adjacency list model, you can generate the structure in one pass.
Taken from One Pass Parent-Child Array Structure (Sep 2007; by Nate Weiner):
$refs = array();
$list = array();
$sql = "SELECT item_id, parent_id, name FROM items ORDER BY name";
/** #var $pdo \PDO */
$result = $pdo->query($sql);
foreach ($result as $row)
{
$ref = & $refs[$row['item_id']];
$ref['parent_id'] = $row['parent_id'];
$ref['name'] = $row['name'];
if ($row['parent_id'] == 0)
{
$list[$row['item_id']] = & $ref;
}
else
{
$refs[$row['parent_id']]['children'][$row['item_id']] = & $ref;
}
}
From the linked article, here's a snippet to create a list for output. It is recursive, if there a children for a node, it calls itself again to build up the subtree.
function toUL(array $array)
{
$html = '<ul>' . PHP_EOL;
foreach ($array as $value)
{
$html .= '<li>' . $value['name'];
if (!empty($value['children']))
{
$html .= toUL($value['children']);
}
$html .= '</li>' . PHP_EOL;
}
$html .= '</ul>' . PHP_EOL;
return $html;
}
Related Question:
How to obtain a nested HTML list from object's array recordset?
I have a new idea I think it will be nice.
The idea is this:
in category_parent column we will insert a reference to all parents of this node.
+----+----------------------+-----------------+
| id | category_name | hierarchy |
+----+----------------------+-----------------+
| 1 | cat1 | 1 |
+----+----------------------+-----------------+
| 2 | cat2 | 2 |
+----+----------------------+-----------------+
| 3 | cat3 | 3 |
+----+----------------------+-----------------+
| 4 | subcat1_1 | 1-4 |
+----+----------------------+-----------------+
| 5 | subcat1_2 | 1-5 |
+----+----------------------+-----------------+
| 6 | subsubcat1_1 | 1-4-6 |
+----+----------------------+-----------------+
| 7 | subsubcat1_2 | 1-4-7 |
+----+----------------------+-----------------+
| 8 | subsubcat1_3 | 1-4-8 |
+----+----------------------+-----------------+
| 9 | subsubcat1_3_1 | 1-4-8-9 |
+----+----------------------+-----------------+
| 10 | subsubcat1_3_2 | 1-4-8-10 |
+----+----------------------+-----------------+
| 11 | subsubcat1_3_1_1 | 1-4-8-9-11 |
+----+----------------------+-----------------+
| 12 | subsubsubcat1_3_1_1 | 1-4-8-9-12 |
+----+----------------------+-----------------+
| 13 | subsubsubcat1_3_1_2 | 1-4-8-9-11-13 |
+----+----------------------+-----------------+
| 14 | subsubsubcat1_2_1_3 | 1-4-8-9-11-14 |
+----+----------------------+-----------------+
if you look at my updated table you will notice that every record has an link to its parents, not only the direct one, But also all of parents.
And for that job I made some modification to insert to be:
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
Now lets make your desired queries:
1- all sub categories of cars:
select * from table_name where hierarchy like '1-%'
2- if you need all parent of BLACK you simply type:
select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1'
(you can build that query from php, splitting hierarchy field at '-' char)
3- To see all categories, with level and direct parent:
select *, SUBSTR(hierarchy, 1, (LENGTH(hierarchy) - LENGTH(id) - 1)) as parent, LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '-', '')) as level From table_name
+----+----------------------+-----------------+-----------+--------+
| id | category name | hierarchy | parent | level |
+----+----------------------+-----------------+-----------+--------+
| 1 | cat1 | 1 | | 0 |
+----+----------------------+-----------------+-----------+--------+
| 2 | cat2 | 2 | | 0 |
+----+----------------------+-----------------+-----------+--------+
| 3 | cat3 | 3 | | 0 |
+----+----------------------+-----------------+-----------+--------+
| 4 | subcat1_1 | 1-4 | 1 | 1 |
+----+----------------------+-----------------+-----------+--------+
| 5 | subcat1_2 | 1-5 | 1 | 1 |
+----+----------------------+-----------------+-----------+--------+
| 6 | subsubcat1_1 | 1-4-6 | 1-4 | 2 |
+----+----------------------+-----------------+-----------+--------+
| 7 | subsubcat1_2 | 1-4-7 | 1-4 | 2 |
+----+----------------------+-----------------+-----------+--------+
| 8 | subsubcat1_3 | 1-4-8 | 1-4 | 2 |
+----+----------------------+-----------------+-----------+--------+
| 9 | subsubcat1_3_1 | 1-4-8-9 | 1-4-8 | 3 |
+----+----------------------+-----------------+-----------+--------+
| 10 | subsubcat1_3_2 | 1-4-8-10 | 1-4-8 | 3 |
+----+----------------------+-----------------+-----------+--------+
| 11 | subsubcat1_3_1_1 | 1-4-8-9-11 | 1-4-8-9 | 4 |
+----+----------------------+-----------------+-----------+--------+
| 12 | subsubsubcat1_3_1_1 | 1-4-8-9-12 | 1-4-8-9 | 4 |
+----+----------------------+-----------------+-----------+--------+
| 13 | subsubsubcat1_3_1_2 | 1-4-8-9-11-13 |1-4-8-9-11 | 5 |
+----+----------------------+-----------------+-----------+--------+
| 14 | subsubsubcat1_2_1_3 | 1-4-8-9-11-14 |1-4-8-9-11 | 5 |
+----+----------------------+-----------------+-----------+--------+
This is a new idea and need some improvement.
Try the following code
//connect to mysql and select db
$conn = mysqli_connect('localhost', 'user', 'password','database');
if( !empty($conn->connect_errno)) die("Error " . mysqli_error($conn));
//call the recursive function to print category listing
category_tree(0);
//Recursive php function
function category_tree($catid){
global $conn;
$sql = "select * from category where parent_id ='".$catid."'";
$result = $conn->query($sql);
while($row = mysqli_fetch_object($result)):
$i = 0;
if ($i == 0) echo '<ul>';
echo '<li>' . $row->cat_name;
category_tree($row->id);
echo '</li>';
$i++;
if ($i > 0) echo '</ul>';
endwhile;
}
//close the connection
mysqli_close($conn);
?>
More...
#Amnon Your code works perfectly. Just tested it with CodeIgniter and it worked like a charm. Here's the working code if anyone needs it:
<?php
function disTree($all_cats) {
$tree = array();
foreach ($all_cats as $cat)
{
$pid = $cat->parent_id;
$id = $cat->cat_id;
$name = $cat->cat_name;
// Create or add child information to the parent node
if (isset($tree[$pid]))
// a node for the parent exists
// add another child id to this parent
$tree[$pid]["children"][] = $id;
else
// create the first child to this parent
$tree[$pid] = array("children"=>array($id));
// Create or add name information for current node
if (isset($tree[$id]))
// a node for the id exists:
// set the name of current node
$tree[$id]["name"] = $name;
else
// create the current node and give it a name
$tree[$id] = array( "name"=>$name );
}
return $tree;
}
function toUL($tree, $id, $html){
$html .= '<ul>'.PHP_EOL;
if (isset($tree[$id]['name']))
$html .= '<li>' . $tree[$id]['name'];
if (isset($tree[$id]['children']))
{
$arChildren = &$tree[$id]['children'];
$len = count($arChildren);
for ($i=0; $i<$len; $i++) {
$html .= toUL($tree, $arChildren[$i], "");
}
$html .= '</li>'.PHP_EOL;
}
$html .= '</ul>'.PHP_EOL;
return $html;
}
$tree = disTree($all_cats);
// Display the tree
echo toUL($tree, 0, "");
?>
The only thing I changed was adding my own array ($all_cats).
There's another way to achieve the same effect which I find a bit easier to follow (without the reference trick). You build the tree by adding the relevant information to the current node and to its parent (assume the foreach iterates over the returned rows from the SQL query):
$tree = array();
foreach ($query->result() as $row)
{
$pid = $row->parent_id;
$id = $row->id;
$name = $row->name;
// Create or add child information to the parent node
if (isset($tree[$pid]))
// a node for the parent exists
// add another child id to this parent
$tree[$pid]["children"][] = $id;
else
// create the first child to this parent
$tree[$pid] = array("children"=>array($id));
// Create or add name information for current node
if (isset($tree[$id]))
// a node for the id exists:
// set the name of current node
$tree[$id]["name"] = $name;
else
// create the current node and give it a name
$tree[$id] = array( "name"=>$name );
}
return $tree;
and to display the tree:
function toUL($tree, $id, $html){
$html .= '<ul>'.PHP_EOL;
if (isset($tree[$id]['name']))
$html .= '<li>' . $tree[$id]['name'];
if (isset($tree[$id]['children']))
{
$arChildren = &$tree[$id]['children'];
$len = count($arChildren);
for ($i=0; $i<$len; $i++) {
$html .= toUL($tree, $arChildren[$i], "");
}
$html .= '</li>'.PHP_EOL;
}
$html .= '</ul>'.PHP_EOL;
return $html;
}
// Display the tree
echo toUL($tree, 0, "");

Categories