I have two tables reporting,employee_details
reporting contains suprvisor_id,subordinate_id fields which are emp_id in employee_details table .And reporting table contains three levels (supervisors->subordinates->subordinates->employees) and i want to show this data as dropdown by fetching name from employee_details table, as hierarchy .so please help me is there any way to do it?
You defititly need to get the hole tree in an array if you want to display it. And this is a little bit tricky.
I think you arent't really shure how many levels you have in tree.
So the simplest way is the following but in big trees it have a bad performance. When the tree grows really big you need to check other technics.
The following is fast written, not tested an could have some bugs. But I post it to show you a possible solution:
<?php
class tree {
private $level = 0;
public function getChildsRecoursive($parentid=0,$recoursive=false) {
// you would have your own db object... please replace this...
$db->select("select * from reporting r JOIN employee_details d ON(r.subordinate_id=d.emp_id) where supervisor_id='$parentid' ORDER BY d.name");
$r = array();
while($data = $db->fetchArray()) {
$sid = $data['supervisor_id'];
$cid = $data['subordinate_id'];
if($recoursive) {
$this->level++;
$data['level'] = $this->level;
$data['childs'] = $this->getChildsRecoursive($cid, true);
$this->level--;
}
$r[] = $data;
}
return $r;
}
public function getDropdown() {
// I suspect the top Level have a supervisor_id = 0
$data = $this->getChildsRecoursive(0,true);
// you can do a print_r($data) here to see if the results are correct
$r = "<select>";
foreach($data as $d) {
$r .= $this->getOptionsRecoursive($d);
}
$r .= "</select>";
return $r;
}
public function getOptionsRecoursive($data) {
$r = "<option>";
for($i=0;$i<$data['level'];$i++) {
$r .= " ";
}
$r .= $data['name'];
$r .= "</option>\n";
if(isset($data['childs'])) {
foreach($data['childs'] as $c) {
$r .= $this->getOptionsRecoursive($c);
}
}
return $r;
}
}
?>
Hope it helps to understand. (Please recognize that this will cause many queries depending on how big your tree is).
To Start you need to do
$tree = new tree();
echo $tree->getDropdown();
Related
Just a simple function to build breadcrumbs based off a simple database table where there are columns:
id, parent, title
The "parent" column can be 0 for top-level, or the "id" of another row for sub-categories (unlimited depth). All I'm trying to do is have a function that'll help me build breadcrumbs from it, starting from the "id" of the bottom-most category I specify and working its way up to the top parent. Here's what I have:
function getCategoryParents($id=0, $out=array()) {
$id = (int)$id;
if ($id > 0) {
$query = "SELECT * FROM `" .$this->tables['categories']. "` WHERE (`id`=" .$id. ")";
$cat = sqlfetch(sqlquery($query));
array_push($out, $cat);
if ($cat['parent']) {
$this->getCategoryParents($cat['parent'], $out);
} else {
return $out;
}
} else {
return $out;
}
}
But it's not returning anything. But, if I do a print_r($out) from within the function instead of return, ex:
if ($cat['parent']) {
$this->getCategoryParents($cat['parent'], $out);
} else {
echo print_r($out);
}
It outputs the array just fine. But I need to be able to actually work with the array, so I need it returned from the function. I'm sure I'm missing something incredibly small, but I cannot figure it out for the life of me!
Also, I don't want to do a global variable, and also need the info of the category id specified in the initial function call as part of the array as well (as I was trying to do).
Following the suggestions, here's what ended up working:
function getCategoryParents($id=0) {
$out = array();
$id = (int)$id;
if ($id > 0) {
$query = "SELECT * FROM `" .$this->tables['categories']. "` WHERE (`id`=" .$id. ")";
$cat = sqlfetch(sqlquery($query));
array_push($out, $cat);
if ($cat['parent']) {
$out = array_merge($out, $this->getCategoryParents($cat['parent']));
}
}
return $out;
}
I'm working on a project in which I pull various statistics about the NHL and inserting them into an SQL table. Presently, I'm working on the scraping phase, and have found an XML parser that I've implemented, but I cannot for the life of me figure out how to pull information from it. The table can be found here -> http://www.tsn.ca/datafiles/XML/NHL/standings.xml.
The parser supposedly generates a multi-dimmensional array, and I'm simply trying to pull all the stats from the "info-teams" section, but I have no idea how to pull that information from the array. How would I go about pulling the number of wins Montreal has? (Solely as an example for the rest of the stats)
This is what the page currently looks like -> http://mattegener.me/school/standings.php
here's the code:
<?php
$strYourXML = "http://www.tsn.ca/datafiles/XML/NHL/standings.xml";
$fh = fopen($strYourXML, 'r');
$dummy = fgets($fh);
$contents = '';
while ($line = fgets($fh)) $contents.=$line;
fclose($fh);
$objXML = new xml2Array();
$arrOutput = $objXML->parse($contents);
print_r($arrOutput[0]); //This print outs the array.
class xml2Array {
var $arrOutput = array();
var $resParser;
var $strXmlData;
function parse($strInputXML) {
$this->resParser = xml_parser_create ();
xml_set_object($this->resParser,$this);
xml_set_element_handler($this->resParser, "tagOpen", "tagClosed");
xml_set_character_data_handler($this->resParser, "tagData");
$this->strXmlData = xml_parse($this->resParser,$strInputXML );
if(!$this->strXmlData) {
die(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($this->resParser)),
xml_get_current_line_number($this->resParser)));
}
xml_parser_free($this->resParser);
return $this->arrOutput;
}
function tagOpen($parser, $name, $attrs) {
$tag=array("name"=>$name,"attrs"=>$attrs);
array_push($this->arrOutput,$tag);
}
function tagData($parser, $tagData) {
if(trim($tagData)) {
if(isset($this->arrOutput[count($this->arrOutput)-1]['tagData'])) {
$this->arrOutput[count($this->arrOutput)-1]['tagData'] .= $tagData;
}
else {
$this->arrOutput[count($this->arrOutput)-1]['tagData'] = $tagData;
}
}
}
function tagClosed($parser, $name) {
$this->arrOutput[count($this->arrOutput)-2]['children'][] = $this->arrOutput[count($this- >arrOutput)-1];
array_pop($this->arrOutput);
}
}
?>
add this search function to your class and play with this code
$objXML = new xml2Array();
$arrOutput = $objXML->parse($contents);
// first param is always 0
// second is 'children' unless you need info like last updated date
// third is which statistics category you want for example
// 6 => the array you want that has wins and losses
print_r($arrOutput[0]['children'][6]);
//using the search function if key NAME is Montreal in the whole array
//result will be montreals array
$search_result = $objXML->search($arrOutput, 'NAME', 'Montreal');
//first param is always 0
//second is key name
echo $search_result[0]['WINS'];
function search($array, $key, $value)
{
$results = array();
if (is_array($array))
{
if (isset($array[$key]) && $array[$key] == $value)
$results[] = $array;
foreach ($array as $subarray)
$results = array_merge($results, $this->search($subarray, $key, $value));
}
return $results;
}
Beware
this search function is case sensitive it needs modifications like match to
a percentage the key or value changing capital M in montreal to lowercase will be empty
Here is the code I sent you working in action. Pulling the data from the same link you are using also
http://sjsharktank.com/standings.php
I have actually used the same exact XML file for my own school project. I used DOM Document. The foreach loop would get the value of each attribute of team-standing and store the values. The code will clear the contents of the table standings and then re-insert the data. I guess you could do an update statement, but this assumes you never did any data entry into the table.
try {
$db = new PDO('sqlite:../../SharksDB/SharksDB');
$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
} catch (Exception $e) {
echo "Error: Could not connect to database. Please try again later.";
exit;
}
$query = "DELETE FROM standings";
$result = $db->query($query);
$xmlDoc = new DOMDocument();
$xmlDoc->load('http://www.tsn.ca/datafiles/XML/NHL/standings.xml');
$searchNode = $xmlDoc->getElementsByTagName( "team-standing" );
foreach ($searchNode as $searchNode) {
$teamID = $searchNode->getAttribute('id');
$name = $searchNode->getAttribute('name');
$wins = $searchNode->getAttribute('wins');
$losses = $searchNode->getAttribute('losses');
$ot = $searchNode->getAttribute('overtime');
$points = $searchNode->getAttribute('points');
$goalsFor = $searchNode->getAttribute('goalsFor');
$goalsAgainst = $searchNode->getAttribute('goalsAgainst');
$confID = $searchNode->getAttribute('conf-id');
$divID = $searchNode->getAttribute('division-id');
$query = "INSERT INTO standings ('teamid','confid','divid','name','wins','losses','otl','pts','gf','ga')
VALUES ('$teamID','$confID','$divID','$name','$wins','$losses','$ot','$points','$goalsFor','$goalsAgainst')";
$result= $db->query($query);
}
I've been building a little tool to manage a dataset, I'm trying create a JSON output in order to serve my front-end the data.
Right now I have an extra comma at the end of every row in the loop. I need to remove it, it would be ideal if I can find a way to do this inside of the while loop.
Here is my code:
$sth = mysql_query("SELECT * FROM mapdata");
$num_rows = mysql_num_rows($sth);
$counter = 0;
echo '[';
while($r = mysql_fetch_assoc($sth)) {
if (++$counter == $num_rows) {
echo json_encode($r) . '';
}
else {
echo json_encode($r) . ',';
}
}
echo "]";
mysql_close($connection);
This is what I'm getting returned now
[
{"col1":"123","col2":"456","col3":"789",},
{"col1":"123","col2":"456","col3":"789",}
]
This is what I need.
[
{"col1":"data1","col2":"data2","col3":"data3"},
{"col1":"data1","col2":"data2","col3":"data3"}
]
Any suggestions would be greatly appreciated.
Your whole approach is wrong, you shouldn't try to create JSON by hand. Put all the rows in an array, and let json_encode() do it all for you.
$result = array();
while ($r = mysql_fetch_assoc($sth)) {
$result[] = $r;
}
echo json_encode($result);
I like to follow best practice whereever possible, but in this particular instance I needed to deviate from best practice due to some environment restrictions I had. I thought I'd share the solution I ended up using, regardless of it being overly complicated.
$sth = mysql_query("SELECT name,address,address2,city,state,postal,phone,lat,lng FROM mapdata");
$num_rows = mysql_num_rows($sth);
$counter = 0;
echo '[';
while($r = mysql_fetch_assoc($sth)) {
if (++$counter == $num_rows) {
echo preg_replace("/^,/",'',str_replace(',}','}',json_encode($r)));
}
else {
echo preg_replace("/^,/",'',str_replace(',}','}',json_encode($r)) . ',');
}
}
echo "]";
mysql_close($connection);
I have a set of structured data, that I'm trying to merge together, based on a set of conditions.
There is a set of rows, returned from the db. Each row has a course id, and a discipline id. There's an equal amount of disciplines in each course, but some disciplines are repeated in both courses.
I want to build a data structure where if the discipline is in both courses, then it only appears once on a line in a new data structure, and nothing else, but if there are two unmatched disciplines, then they are both included in a new course.
The code I'm using so far, is filtering and removing the keys that are duplicated, and adding them to a new array. This works fine.
However, because I can't control the order in which the data comes (don't ask) I'm having troubles making sure that each line has either a discipline that appears in both courses, or one of each.
I think I need some techniques to help deal with this, and was wondering if anyone had come across this before. I want to avoid making many consecutive loops, if possible.
Thanks.
edit: messy and horrible code below:
function buildDisciplineMap(){
$sql = "SELECT [idcurso]
,[idversao]
,[ordem]
,[bloco]
,[obsbloco]
,[iddisciplina] as idd
,cast([iddisciplina] as TEXT) as iddisciplina
,[descdisciplina]
,[iddepartamento]
,[descdepartamento]
,[ects]
,[horas]
,[idregente]
,[regente]
,[idregente1]
,[regente1]
,[idregente2]
,[regente2]
,[idregente3]
,[regente3]
,cast([objectivos] as TEXT) as objectivos
,cast([programa] as TEXT) as programa
,[descdisciplina_en]
,cast([objectivos_en] as TEXT) as objectivos_en
,cast([programa_en] as TEXT) as programa_en
FROM [proffile2].[dbo].[vw_site_CursosDisciplinas_FEG]
where idcurso = '3512 GE' or idcurso = '3513 ECON' order by idcurso desc ";
$discs = $this->returnObject($sql);
$map = new stdClass();
// find blocos, and titles
foreach ($discs as $key => $value) {
if (isset($map->bloco[$value->bloco])) {
// block already exists
} else {
#echo "making new block";
$map->bloco[$value->bloco] = new stdClass();
}
if (strlen($value->obsbloco)>1) {
$map->bloco[$value->bloco]->title = $value->obsbloco;
}
}
foreach ($map->bloco as $keybloco => $value) {
$map->bloco[$keybloco]->lines = array();
$processed_ids = array();
foreach ($discs as $kd => $vd) {
if ($vd->bloco == $keybloco) {
// check if this discipline occurs more than once in this block
foreach ($discs as $kdd => $vdd) {
if ($vdd->iddisciplina == $vd->iddisciplina && $kdd != $kd && !in_array($kd,$processed_ids) && !in_array($kdd,$processed_ids)) {
// this discipline is for both courses
$details = array();
$details['both'] = $vd;
$map->bloco[$keybloco]->lines[] = $details;
array_push($processed_ids, $kd, $kdd);
unset($discs[$kdd]);
unset($discs[$kd]);
break;
}
}
}
}
}
$processed_ids = array();
foreach ($discs as $key => $value) {
echo $value->idcurso."\n";
}
foreach ($discs as $kd => $vd) {
$bloco = $vd->bloco;
$lastidx =sizeof($map->bloco[$bloco]->lines)-1;
$line = $map->bloco[$bloco]->lines[$lastidx];
echo sizeof($map->bloco[$bloco]->lines);
#pr($line);
if (isset($line['both'])) {
echo "omog - \n ";
$map->bloco[$bloco]->lines[][$vd->idcurso] = $vd;
unset($discs[$kd]);
continue;
}
#pr($line['both']->idcurso);
foreach ($map->bloco[$bloco]->lines as $k => $v) {
echo $k."-";
#echo $v['3513 ECON']->idcurso;
}
if ($line[$vd->idcurso]) {
echo 'add';
$map->bloco[$bloco]->lines[][$vd->idcurso] = $vd;
} else {
echo 'fill';
$map->bloco[$bloco]->lines[sizeof($map->bloco[$bloco]->lines)-1][$vd->idcurso] = $vd;
}
}
echo sizeof($discs);
return $map;
}
You said "don't ask", but I've got to: why can't you control the order of the rows? Aren't you the one writing the database query?
If you think fixing the order of the rows will help you parse and build a new data structure better, why not make sorting the rows the first step in your process? Or, is the data set too large?
You might find some of PHP's array set manipulations to be of use. i.e. array_diff, array_intersect_key etc.
I'm creating a tree-structure of categories with parentid's which can be called from children in such a way:
ID | Name | ParentID
1 1 0
2 2 1
3 3 2
4 4 1
Resulting in this:
1 = 1
2 = 1 -> 2
3 = 1 -> 2 -> 3
4 = 1 -> 4
which means 3 is a child of 2, which is a child of 1.
when trying to get this idea (with the -> to show what relations are set) I only get to the second grade (1 -> 2) but not to the third (1->2->3) because of the looping function I use for it.
//put all ID's in an array
while ($row2 = $connector->fetchArray($result2)){
$id = $row2['ID'];
$parents[$id] = $row2['name'];
}
// show the tree-structure
while ($row = $connector->fetchArray($result)){
if($row['parentid']!=0)echo $parents[$row['parentid']].' -> ';
echo $row['name'].' - ';
echo '<br>';
}
I'd like two things to change:
have the code automatically generate a tree sized as necessary.
in the while-loops i have to select the $result twice (once as $result, once as $result2) to make it work. these $result's have exactly the same database-query:SELECT ID,name,parentid FROM categories
to fetch results from. I'd like to only declare this once.
Thanks for all the good answers. I've gone with the easiest, less-code-to-implement approach:
$result = $connector->query('SELECT ID,name,parentid FROM categories');
// Get an array containing the results.
$parents = array();
while ($row = $connector->fetchArray($result)){
$id = $row['ID'];
$parents[$id] = array('ID' => $row['ID'],'name' => $row['name'],'parentid' => $row['parentid']);
}
foreach ($parents as $id => $row){
$pid=$id;
$arrTmp= array();
do { // iterate through all parents until top is reached
$arrTmp[]=$pid;
$pid = $parents[$pid]['parentid'];
}while ($pid != 0);
$arrTmp = array_reverse($arrTmp);
foreach($arrTmp as $id){
echo $parents[$id]['name'].' -> ';
}
echo '<br>';
}
Rather than have PHP organize the items into a tree, why not ask the database to do it for you? I found this article on hierarchical data to be very good and the examples are almost identical to yours.
EDIT
The SQL for getting the full tree using the Adjacency Model is not ideal. As the article explains it requires rather a lot of joins for even a small hierarchy. Is it not possible for you to use the Nested Set approach? The SQL stays the same regardless of the size of the hierarchy and INSERT and DELETE shouldn't be very difficult either.
If you really want to do hierachies with parent ids(suitable only for small number of items/hierachies)
I modified your code a little bit(I did not test it so there may be some syntax errors):
//put all recordsets in an array to save second query
while ($row2 = $connector->fetchArray($result2)){
$id = $row2['ID'];
$parents[$id] = array('name' => $row2['name'],'parent' => $row2['parentid']);
}
// show the tree-structure
foreach ($parents as $id => $row){
$pid = $row['parentid'];
while ($pid != 0){ // iterate through all parents until top is reached
echo $parents[$pid]['name'].' -> ';
$pid = $parents[$pid]['parentid'];
}
echo $parents[$id]['name'].' - ';
echo '<br>';
}
To answer your comment:
$parents = array();
$parents[2] = array('ID'=>2,'name'=>'General','parentid'=>0);
$parents[3] = array('ID'=>3,'name'=>'Gadgets','parentid'=>2);
$parents[4] = array('ID'=>4,'name'=>'iPhone','parentid'=>3);
foreach ($parents as $id => $row){
$pid=$id;
$arrTmp= array();
do { // iterate through all parents until top is reached
$arrTmp[]=$pid;
$pid = $parents[$pid]['parentid'];
}while ($pid != 0);
$arrTmp = array_reverse($arrTmp);
foreach($arrTmp as $id){
echo $parents[$id]['name'].' -> ';
}
echo '<br>';
}
Prints out:
General ->
General -> Gadgets ->
General -> Gadgets -> iPhone ->
Maybe easier with OOP. Just sort the query by parentId
Note: The listChildren method and the printout at the bottom is just there to show it is listed correctly. I did not interpret the question that the display was important.
class Element {
public $id;
public $name;
public $parent = null;
public $children = array();
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
public function addChild($element)
{
$this->children[$element->id] = $element;
$element->setParent($this);
}
public function setParent($element)
{
$this->parent = $element;
}
public function hasChildren()
{
return !empty($this->children);
}
public function listChildren()
{
if (empty($this->children)) {
return null;
}
$out = array();
foreach ($this->children as $child) {
$data = $child->id . ':' . $child->name;
$subChildren = $child->listChildren();
if ($subChildren !== null) {
$data .= '[' . $subChildren . ']';
}
$out[] = $data;
}
return implode(',', $out);
}
}
$elements = array();
$noParents = array();
while ($row = $connector->fetchArray($result)) {
$elements[$row['id']] = $element = new Element($row['id'], $row['name']);
if (isset($elements[$row['parent']])) {
$elements[$row['parent']]->addChild($element);
} else {
$noParents[] = $element;
}
}
foreach ($noParents as $element) {
if ($element->hasChildren()) {
echo "Element {$element->id} has children {$element->listChildren()}.\n";
} else {
echo "Element {$element->id} has no children.\n";
}
}
If you are using PostgreSQL as the database, you can use the connectby() function to create the record set:
SELECT *
FROM connectby('tableName', 'id', 'parent_id')
AS t(keyid text, parent_keyid text, level int);
I love this function, and use all the time in my code. It can do some very powerful things, very quickly, and you don't have maintain the left/right values like the (adjacency model).