Modified Preorder Tree Traversal Algoritm #2
Modified preorder tree traversal is one of the meanings, how to save tree structure in relational database. We will show in this article, how we can erase or move embranchments from created tree.
This article continues to last article “modified preorder tree traversal algorithm #1”, where were explained basic principles of algorithm and we showed basic principles of creating of tree. If you haven’t read, I recommend you to read it.
So we have created table in our database, which we are connecting to, and we have saved structure in that. By each element (embranchment) we have saved left and right index and index of nesting in tree.
Erasing embranchments from tree
Modified preorder tree traversal is algorithm very pleasant, if we look at this from view of dates selecting, indeed from view of editing we can’t make any operation with only one SQL query.
It’s the same like with erasing of embranchments from tree. It isn’t possible to erase one row from table simply, because if we do so, it happen, that left and right indexes wouldn’t agree. So it is important, to check indexes and renumbered tree after erasing of each embranchment.
Method, which we create first, will control, if in embranchment, which we want erase, are no more embranchments. Of course it wouldn’t be problem to erase also the whole parts of tree, but it is absolutely needles. If we want erase one whole part of tree, we can erase it each embranchment singly.
So our class will be enabled to erase only embranchments, which don’t branch out any more, it means, only those, which are on the end of tree.
Method for checking, if embranchment doesn’t branch out any more, will be very simple. Principe is in it, that we will know right and left index of the embranchment, which we are erasing, and we must find out, if there is no embranchment, which has left index between these two embranchments. We must know left and right index in this method, which we are erasing.
Left index will be input value of whole method for erasing embranchment and right index we find out with method “select_parent”, which we created in last article.
So we know both indexes and we can finally create method:
1 2 3 4 5 6 7 8 9 10 11 |
table_name . " WHERE ( traverz_left BETWEEN '" . $left . "' AND '" . $this->traverz_right . "' ) AND lang = '" . $_SESSION['lang_prefix'] . "'"; $num = intval( @ mysql_result(mysql_query($select, $this->link), 0, 0) - 1 ); return ( $num == 0 ? 1 : 0 ); } |
Method _checkTree has return value 1 or 0 in case of finding or non-finding of embranchment. Next method, which we will need, will edit right and left index after erasing.
In last article about traversal algorithm we have created method _update_tree, which care about renumbering of indexes after adding of embranchments. Surely you feel yet, how will method for renumbering looks like after erasing.
Method will be nearly identical, only with different, that instead of rising of indexes we will decline indexes now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function _renumberTree($left){ $update[] = "UPDATE " . $this->table_name . " SET traverz_left = (traverz_left - 2) WHERE ( traverz_left > '" . intval( $left ) . "') AND lang = '" . $_SESSION['lang_prefix'] . "'"; $update[] = "UPDATE " . $this->table_name . " SET traverz_right = (traverz_right - 2) WHERE ( traverz_right > '" . intval( $left + 1 ) . "') AND lang = '" . $_SESSION['lang_prefix'] . "'"; foreach($update as $value){ mysql_query( $value, $this->link ); if( mysql_affected_rows( $this->link ) < 0 ) return 0; } return 1; } |
In this method we need know both indexes again, to renumber only good embranchment. We will renumber only those, which has bigger index than erased embranchment.
When we have created methods for checking and renumbering of tree, we can finally erase embranchments. Before erasing we will control, if embranchment doesn’t branch out any more and nearly before erasing we will renumber tree. This operation we will make before erasing from this reason, that if renumbering didn’t do right, erasing don’t happen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function deleteTree( $left ){ $left = intval( $left ); $parent = self::select_parent($left, 1); if(self::_checkTree( $left ) == 0) return -1; if( self::_renumberTree($left) == 0 ) return 0; $delete = "DELETE FROM " . $this->table_name . " WHERE id_traverz = '" . $this->id_traverz . "' LIMIT 1;"; mysql_query($delete, $this->link); return ( mysql_affected_rows( $this->link ) == 0 ? 0 : 1 ); } |
This method return in case of true value one. In case, that other embranchments exists, which are nesting in erasing embranchment, return value -1. In other cases it returns value 0, which symbolize false.
Moving of embranchments in tree
Moving of embranchments is perhaps the most complicated operation, which we will make in tree. For moving of two embranchments we will have to know their left and right indexes. Left indexes will be directly input parameters of method for moving. These input parameters we must check, if they have same level of nesting. If isn’t this reality true, we won’t moving. So we will be limitation again to it, that two embranchment, which we are moving, must have the same level nesting.
We know left index, but we don’t know right index and level of nesting. So we create method for findings these values first.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function _select_parent_check($left1, $left2){ $select = "SELECT traverz_parent, traverz_right FROM " . $this->table_name . " WHERE traverz_left IN ('" . $left1 . "', '" . $left2 . "') AND lang = '" . $_SESSION['lang_prefix'] . "' ORDER BY traverz_left;"; if( ( $data = mysql_query($select, $this->link) ) == FALSE ){ return 0; } return array( @mysql_result($data, 0, 0), @mysql_result($data, 1, 0), @mysql_result($data, 0, 1), @mysql_result($data, 1, 1) ); } |
Method returns array, which is saved right index and level of nesting both embranchments in. This method we call with next method, which we need:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function _checkingTree($left1, $left2){ $parents = self::_select_parent_check($left1, $left2); if( sizeof($parents) != 4 ) return 0; else if( $parents[0] != $parents[1] ) return 0; $select = "SELECT traverz_parent, traverz_left, traverz_right FROM " . $this->table_name . " WHERE traverz_left < '" . ( $left1 < $left2 ? $left1 : $left2) . "' AND traverz_parent = '" . ($parents[0] - 1) . "' AND lang = '" . $_SESSION['lang_prefix'] . "' ORDER BY traverz_left DESC LIMIT 1;"; $data = mysql_query($select, $this->link); if( mysql_error() != NULL ) return 0; $h1 = @mysql_result($data, 0, 1); $h2 = @mysql_result($data, 0, 2); //test left1 if( !( ( $h1 < $left1 ) && ( $h2 > $left2 ) ) ){ return 0; } //test left2 else if( !(($h1 < $left2) && ($h2 > $left2)) ){ return 0; } else { return $parents; } } |
The method _checkingTree checks values of nesting and checks indexes moreover. Return value is array, which contain values from method _select_parent_check.
Now we know all needful values and we can change two embranchments between themselves. By changing there can appear some situations:
First of them appear, when two embranchments are just side-by-side and don’t contain other nested embranchments. This case is the most simple and we should change indexes of embranchments only.
Another case is, that two embranchments are side-by-side, but they have nested other embranchments in them. In this case we have to change indexes also in embranchments extra, which are inside of embranchments, which we are changing. It is logic, because right and left embranchments can’t contain same number of nested embranchments and so each index has to be changed.
The most complicated case appears in situation, when both embranchments, which we are changing, contain other embranchments and there are other embranchments between then extra. In this case we must change indexes in all subembranchments of right embranchment, in all subembranchments of left embranchment and moreover change indexes in all embranchments, which are between these two embranchments.
We have to all take regard to all of these cases and we have to make exception to all of these cases. Now we have created only methods for finding information, which we need for moving.
The smarter will make moving alone, others have to wait for next article.
We can look, what is completed at the end:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
class traverz extends connect{ public $traverz_right = NULL; public $id_traverz = NULL; public $parametrs = array(); public $ids = array(); public $table = NULL; function __construct( $table_name ){ $this->table_name = $table_name; parent::__construct(); self::_check_num_of_rows(); } function _check_num_of_rows(){ $select = "SELECT COUNT(*) FROM " . $this->table_name . " WHERE lang = '" . $_SESSION['lang_prefix'] . "'"; if( mysql_result( mysql_query( $select, $this->link ), 0, 0) == 0 ){ self::_insert_first(); return 0; } else{ return 0; } } //Insert HOME into Traverz_table function _insert_first(){ $insert = "INSERT INTO " . $this->table_name . "( traverz_name, traverz_parent, traverz_left, traverz_right, lang ) VALUES( 'HOME', '0', '1', '2', '" . $_SESSION['lang_prefix'] . "', 'home' )"; mysql_query($insert, $this->link); return ( mysql_affected_rows($this->link) == 1 ? 1 : 0); } function _update_tree($id, $left){ $sql[] = "UPDATE " . $this->table_name . " SET traverz_left = (traverz_left + 2) WHERE ( traverz_left > '" . $left . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; $sql[] = "UPDATE " . $this->table_name . " SET traverz_right = (traverz_right + 2) WHERE ( traverz_right > '" . ($left - 1) . "') AND id_traverz != '" . $id . "' AND lang = '" . $_SESSION['lang_prefix'] . "'"; foreach($sql as $value){ mysql_query($value, $this->link); if(mysql_affected_rows($this->link) == 0 ) return 0; } } function select_parent($left, $info = 0){ $select = "SELECT traverz_parent, traverz_right, id_traverz FROM " . $this->table_name . " WHERE traverz_left = '" . $left . "' AND lang = '" . $_SESSION['lang_prefix'] . "' LIMIT 1;"; $data = mysql_query($select, $this->link); if( $info == 1 ){ $this->traverz_right = @mysql_result($data, 0, 1); $this->id_traverz = @mysql_result( $data, 0, 2 ); } return ( @mysql_result($data, 0, 0) + 1 ); } function _setup_position( $position ){ switch( strtolower( $position ) ){ case 'h' : return 0; break; case 'e' : return 1; break; default: return 0; } } function addTree ( $name, $left, $position = 0 ){ if( gettype( $position ) == 'string' ) $position = self::_setup_position( $position ); $position = abs( intval( $position) ); if( $position > 2 ) return 0; $parent = self::select_parent($left, 1); switch( $position ){ case 0 : { if( self::_addTree_h( $name, $left, $parent ) == 1 ) return 1; else return 0; break; } case 1 :{ if( self::_addTree_e( $name, $left, $parent, ( $this->traverz_right - 1 ) ) == 1 ) return 1; else return 0; break; } } return 1; } function _addTree_h( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "( traverz_name, traverz_parent, traverz_left, traverz_right, lang ) VALUES( '" . trim( htmlspecialchars($name) ) . "', '" . intval( $parent ) . "', '" . ( intval( $left ) + 1 ) . "', '" . ( intval( $left ) + 2 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $left) == 1 ? 1 : 0 ); else return 0; } function _addTree_e( $name, $left, $parent ){ $insert = "INSERT INTO " . $this->table_name . "(traverz_name, traverz_parent, traverz_left, traverz_right, lang) VALUES( '" . trim( htmlspecialchars( $name ) ) . "', '" . intval( $parent ) . "', '" . intval( $this->traverz_right ) . "', '" . ( intval( $this->traverz_right ) + 1 ) . "', '" . $_SESSION['lang_prefix'] . "', );"; mysql_query($insert, $this->link); if( mysql_affected_rows($this->link) == 1 ) return (self::_update_tree(mysql_insert_id(), $this->traverz_right) == 1 ? 1 : 0 ); else return 0; } } |