I have a DB like so:
id text parent
1 Parent 1 0
2 Child of 1 1
3 Sibling 1
4 Another Parent 0
5 A first child 4
So I'm trying to capture a tree structure my listing the parents. I'm aware of the other option (nested sets I think?) but I'm going to stick with this for now. I'm now trying to get the data out of the DB and into a nested array structure in PHP. I have a function like this:
class Data_Manager
{
public $connection = '';
public $collection = array();
function __construct() {
$this->connection = mysql_connect('localhost', 'root', 'root');
$thisTable = mysql_select_db('data');
// error handling truncated
}
function get_all() {
$arr = &$this->collection;
$this->recurseTree('', 0, $arr);
var_dump($arr);
}
function recurseTree($parent, $level, $arrayNode) {
$result = mysql_query('SELECT * FROM tasks WHERE parent="' . $parent . '";');
while ($row = mysql_fetch_array($result)) {
$row['children'] = array(); //where I'd like to put the kids
$arrayNode[$row['id']]= $row;
$this->recurseTree($row['id'], $level+1, $arrayNode[$row['id']]);
}
}
}
So what I'd like to come out with is some kind of nested tree of associative arrays, but I can't figure out quite how to do that. Nothing seems to be writing to the array I pass in, and I'm sort of losing track of myself in the recursion. Can anyone help get me over this last hump that will result in something like:
[
Parent1 => [
children => ['Child of 1', 'Sibling']
],
AnotherParent => [
children => ['First Child']
]
]
And I'm less concerned with the specific form of the output. It will be turned into JSON and I haven't dealt with writing up the client-side handler yet, so no worries on exact structure.
Thanks!
Try this.
$sql = "SELECT * FROM tasks";
$r = mysql_query($sql, $conn);
$arr = array();
while ($row = mysql_fetch_assoc($r))
$arr[] = $row
function build($arrayIn, $parent)
{
$makeFilter = function($p) {return function($x) use ($p) {return $x['parent'] == $p;};};
$f = $makeFilter($parent);
$these = array_filter($arrayIn, $f);
$remaining = array_diff_assoc($arrayIn, $these);
$ans = array();
foreach($these as $cur)
{
$ans[$cur['text']] = build($remaining, $cur['id']);
}
return $ans ? $ans : null;
}
$tree = build($arr, 0)
echo_r($arr);
echo "becomes<br />";
echo_r($tree);
Here is my output:
Array
(
[0] => Array
(
[text] => a
[id] => 1
[parent] => 0
)
[1] => Array
(
[text] => b
[id] => 2
[parent] => 0
)
[2] => Array
(
[text] => c
[id] => 3
[parent] => 1
)
[3] => Array
(
[text] => d
[id] => 4
[parent] => 2
)
[4] => Array
(
[text] => e
[id] => 5
[parent] => 2
)
[5] => Array
(
[text] => f
[id] => 6
[parent] => 3
)
)
becomes
Array
(
[a] => Array
(
[c] => Array
(
[f] =>
)
)
[b] => Array
(
[d] =>
[e] =>
)
)
This bit of pseudo code should help.
function getTasks($parent = 0){
$tasks = array();
$query = mysql_query("select * from table where parent = $parent");
$rows = array();
while(($row = mysql_fetch_assoc($query)) !== FALSE){ $rows[] = $row; }
if(count($rows)){
$tasks[$parent][] = getTasks($parent);
} else {
return $tasks;
}
}
$tasks = getTasks();
You really don't need a recursive function here. Get all the data using one database query and loop over it. It will be much faster then multiple database calls.
Assuming you're storing the data in MySQL, see the answer to this question for instructions on how to write a SELECT statement against an Adjacency List table that returns everything in a hierarchy. In short, use MySQL session variables. Then take the resultset and loop over it, use a stack to push - pop - peek the last parent id to determine indentation of your data structures.
Here is a PHP class I wrote for handling all kinds of Adjacency list tasks.
http://www.pdvictor.com/?sv=&category=just+code&title=adjacency+model
Related
I have an array of elements which I queried from the mongoDB.
This array has the id of a device and value of this device's consumptions.
For example there are 3 different ids -> 18,5,3 and multiple mixed values.
// first record of 18 so get value.
$row[0]["id"] = 18;
$row[0]["value"] = 100;
// not first record of 18 so ignore and move to the next record
$row[1]["id"] = 18;
$row[1]["value"] = 40;
// first record of 5 so get value.
$row[2]["id"] = 5;
$row[2]["value"] = 20;
// not first record of 18 so ignore and move to the next record
$row[3]["id"] = 18;
$row[3]["value"] = 30;
// first record of 3 so get value.
$row[4]["id"] = 3;
$row[4]["value"] = 20;
//not first record of 5 so ignore and move to the next record**
$row[5]["id"] = 5;
$row[5]["value"] = 30;
// not first record of 18 so ignore and move to the next record
$row[6]["id"] = 18;
$row[6]["value"] = 10;
...
....
What I am trying to do is loop through this $row array and get the most recent value of the id.
For example in above example what I want to return is:
id value
18 100
5 20
3 20
How can I do that kind of logic?
It can be done in several ways. The easiest one is to use a foreach:
$result = array();
foreach ($row as $i) {
if (! array_key_exists($i['id'], $result)) {
$result[$i['id']] = $i['value'];
}
}
# Verify the result
print_r($result);
The output is:
Array
(
[18] => 100
[5] => 20
[3] => 20
)
The same processing, but using array_reduce():
$result = array_reduce(
$row,
function(array $c, array $i) {
if (! array_key_exists($i['id'], $c)) {
$c[$i['id']] = $i['value'];
}
return $c;
},
array()
);
If you want to keep only the first occurrence of each 'id' then just add the values to an aggregate array - but only if they haven't been added already. Then grab the values of the aggregate array.
https://tehplayground.com/NRvw9uJF615oeh6C - press Ctrl+Enter to run
$results = array();
foreach ($row as $r) {
$id = $r['id'];
if (! array_key_exists($id, $results)) {
$results[$id] = $r;
}
}
$results = array_values($results);
print_r($results);
Array
(
[0] => Array
(
[id] => 18
[value] => 100
)
[1] => Array
(
[id] => 5
[value] => 20
)
[2] => Array
(
[id] => 3
[value] => 20
)
)
Try this
$alreadyfound = []; // empty array
$result = [];
foreach ($row as $item)
{
if (in_array($item["id"], $alreadyfound))
continue;
$alreadyfound[] = $item["id"]; // add to array
$result[] = $item;
}
The result
Array
(
[0] => Array
(
[id] => 18
[value] => 100
)
[1] => Array
(
[id] => 5
[value] => 20
)
[2] => Array
(
[id] => 3
[value] => 20
)
)
The array_unique() function is exactly what you are looking at.
See the documentation here : array_unique() documentation
Using array_column with an index key will almost do it, but it will be in the reverse order, so you can reverse the input so that it works.
$result = array_column(array_reverse($row), 'value', 'id');
I have the following output that I am getting from the laravel and I am inserting them in to selectbox as an option. But for the lunch option my last options are empty. How can I return rows in laravel that are not empty?
{"id":18,"guest_origin":"Bulgaria","heard_where":"","staying_at":"","lunch_option":"Cheese"},{"id":19,"guest_origin":"Chech
Republic","heard_where":"","staying_at":"","lunch_option":"Chicken"},{"id":20,"guest_origin":"China","heard_where":"","staying_at":"","lunch_option":"Ham"},{"id":21,"guest_origin":"Denmark","heard_where":"","staying_at":"","lunch_option":""},{"id":22,"guest_origin":"Finland","heard_where":"","staying_at":"","lunch_option":""},{"id":23,"guest_origin":"Israel","heard_where":"","staying_at":"","lunch_option":""},{"id":24,"guest_origin":"Malaysia","heard_where":"","staying_at":"","lunch_option":""},{"id":25,"guest_origin":"Norway","heard_where":"","staying_at":"","lunch_option":""},
controller.php
function getComboselect( Request $request)
{
if($request->ajax() == true && \Auth::check() == true)
{
$param = explode(':',$request->input('filter'));
$parent = (!is_null($request->input('parent')) ? $request->input('parent') : null);
$limit = (!is_null($request->input('limit')) ? $request->input('limit') : null);
$rows = $this->model->getComboselect($param,$limit,$parent);
$items = array();
$fields = explode("|",$param[2]);
foreach($rows as $row)
{
$value = "";
foreach($fields as $item=>$val)
{
if($val != "") $value .= $row->{$val}." ";
}
$items[] = array($row->{$param['1']} , $value);
}
return json_encode($items);
}
Model.php
static function getComboselect( $params , $limit =null, $parent = null)
{
$limit = explode(':',$limit);
$parent = explode(':',$parent);
if(count($limit) >=3)
{
$table = $params[0];
$condition = $limit[0]." `".$limit[1]."` ".$limit[2]." ".$limit[3]." ";
if(count($parent)>=2 )
{
$row = \DB::table($table)->where($parent[0],$parent[1])->get();
$row = \DB::select( "SELECT * FROM ".$table." ".$condition ." AND ".$parent[0]." = '".$parent[1]."'");
} else {
$row = \DB::select( "SELECT * FROM ".$table." ".$condition);
}
}else{
$table = $params[0];
if(count($parent)>=2 )
{
$row = \DB::table($table)->where($parent[0],$parent[1])->get();
} else {
$row = \DB::table($table)->get();
}
}
return $row;
}
This code is using http://carlosdeoliveira.net/jcombo/?lang=en. If you look up to the example on the project link you will see that it is using parent (state) to list the child (cities) for listings. I am not using the parent so nothing is assinged to variables $parent[0] and $parent[ 1 ], thus nothing to worry about but for the rest, I will try to post each result below so, you would have a better idea. My understanding is that the model.php is passing the data to controllers.php using $row = \DB::table($table)->get(); If you look to the screenshot, you will see that I have more then 1 column to list the options. I cannot write a single column name there if I write $row = \DB::table($table)->whereRaw('lunch <> ""')->get(); this brings the options until the Id 4. In this case Holland is not in the option list for guest origin.
Once the model.php passes $row to controllers.php, It' returning the following results for each variable.
print_r($row);
stdClass Object ( [id] => 48 [guest_origin] => Other [heard_where] =>
[staying_at] => [lunch_option] => )
print_r($rows);
Illuminate\Support\Collection Object ( [items:protected] => Array (
[0] => stdClass Object ( [id] => 1 [guest_origin] => Western Australia
[heard_where] => Wildsights Office [staying_at] => Wildsights Villas
[lunch_option] => Chicken ) 1 => stdClass Object ( [id] => 2
[guest_origin] => Rest of Australia [heard_where] => Brochure
[staying_at] => Wildsights Beach Units [lunch_option] => Cheese ) [2]
=> stdClass Object ( [id] => 3 [guest_origin] => Germany & Austria [heard_where] => Sign [staying_at] => Bay Lodge Backpackers
[lunch_option] => Ham ) [3] => stdClass Object ( [id] => 4
[guest_origin] => UK & Eire [heard_where] => Word of Mouth
[staying_at] => Blue Dolphin Caravan Park [lunch_option] => Tuna )
print_r($fields);
Array ( [0] => staying_at )
print_r($value);
prints nothing
print_r($items);
[8] => Array ( [0] => Shark Bay Holiday Cottages 1 => Shark Bay
Holiday Cottages ) [9] => Array ( [0] => Shark Bay Hotel 1 => Shark
Bay Hotel )
Hope it is clear and you can help me to filter the empty rows before it goes into loop.
The most appropriate way would be to use whereRaw operator rather then where.
e.x The following query will fetch all data except empty("") values in the field list.
DB::table('mytable')->whereRaw('field <> ""')->get();
You can use macro, it checks field for not null and not empty string. Just add these lines of code into your AppServiceProvider
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
public function boot()
{
QueryBuilder::macro(
'whereNotEmpty',
function (string $column) {
return $this->whereNotNull($column)
->where($column, '<>', '');
}
);
EloquentBuilder::macro(
'whereNotEmpty',
function (string $column) {
return $this->getQuery()
->whereNotEmpty($column);
}
);
}
And now you allow to use this like that:
$posts = Post::whereNotEmpty('field')->get();
->whereNotNull('<field>')
As an alternative, this works for me
I have build my user table like this:
Now I would like to select the 3 blue rows.
I have build the following function:
function display_children($parent, $array = array()) {
global $db;
$stmt = $db->prepare("SELECT id AS man, parent_id FROM user WHERE parent_id= ?");
$stmt->execute(array($parent));
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
display_children($row['man'], $array);
$array[] = array("man" => $row['man'], "parent_id" => $row['parent_id']);
echo $row['man']."\n"; //for debug
}
return $array;
}
print_r(display_children(50001));
My Output is:
50002
50004
50003
Array
(
[0] => Array
(
[man] => 50002
[parent_id] => 50001
)
[1] => Array
(
[man] => 50003
[parent_id] => 50001
)
)
The first three lines are correct, but the array is missing one.
The problem is, that the first parent_id has two rows. But the array is empty at the beginning. Is there a solution for my query?
the solution was to change the following line from
display_children($row['man'], $array);
to
$array = display_children($row['man'], $array);
This question already has answers here:
get array of rows with mysqli result
(2 answers)
Closed 2 years ago.
please check out my code below. With that class I am able to display results like so:
$connectTest = new testResults();
$test = $connectTest->grabResults(test, id, id);
echo $test['id'];
echo $test['name'];
echo $test['address'];
In my database I have several fields in the "test" table. I go to my page using index.php?id=1. With this I am displaying just the results from one row because it grabs all results WHERE id = 1.
What I need is the class below to display multiple results. It just displays one row. But if I have multiple rows with id = 1 I would like to display these results, but I cannot get it to work. I have tried a lot of things but I always end up with just one result.
class:
class testResults
{
public function grabResults($table, $field, $id)
{
$result = $this->db->mysqli->query("SELECT * FROM $table WHERE $field = $id");
$resultData[] = array();
if(!$result)
{
return false;
}
while($row = $result->fetch_assoc())
{
$rows[] = $row;
}
foreach ($rows as $resultData)
{
return $resultData;
}
}
}
Edit:
Array ( [id] => 25 [name] => test [status] => 1 )
Array ( [id] => 25 [name] => test [status] => 3 )
Array ( [id] => 25 [name] => test [status] => 5 )
Array ( [id] => 25 [name] => test [status] => 4 )
Array ( [id] => 26 [name] => test [status] => 1 )
Array ( [id] => 26 [name] => test [status] => 3 )
Array ( [id] => 27 [name] => test [status] => 1 )
Array ( [id] => 27 [name] => test [status] => 3 )
Array ( [id] => 27 [name] => test [status] => 5 )
Array ( [id] => 27 [name] => test [status] => 4 )
Array ( [id] => 27 [name] => test [status] => 2 )
Array ( [id] => 27 [name] => test [status] => 4 )
Array ( [id] => 27 [name] => test [status] => 1 )
I am getting results as above, any way to easily display these results in an echo? For each id there are different results, so results will vary with each query. So I would like to display results in a table for example like so:
echo '<table>
<tr>
<td>$id</td>
<td>$name</td>
<td>$status</td>
</tr>
</table>';
So all results will be displayed like in a while loop.
You can just return the array from function and then loop in your script
while($row = $result->fetch_assoc())
{
$rows[] = $row;
}
return $rows;
The you can loop in your script
$test = $connectTest->grabResults(test, id, id);
foreach($test as $value)
{
print_r($value);
}
Upon OP edit
If you need to print them separate you can access all elements with variable name and scopes with keys as follow
$test = $connectTest->grabResults(test, id, id);
echo '<table>';
foreach($test as $value)
{
echo '<tr>
<td>'.$value['id'].'</td>
<td>'.$value['name'].'</td>
<td>'.$value['status'].'</td>
</tr>';
}
echo '</table>';
Since you are passing the full resultset to another layer for processing, you can skip the loop to generate an array of associative arrays from the resultset.
class testResults {
public function grabResults($table, $field, $id) {
// For the record, I feel a prepared statement is in order here...
$result = $this->db->mysqli->query("SELECT * FROM $table WHERE $field = $id");
return $result->fetch_all(MYSQLI_ASSOC); // just in case you wanted to see the column names
}
}
Then when you want to generate an html table from the returned array of associative arrays, use implode() as a flexible solution that doesn't care if you ever change the number of columns being passed in -- it will handle an indefinite number of columns.
if ($resultset = grabResults("tests", "name", "test")) {
echo "<table>";
foreach ($resultset as $i => $row) {
// if (!$i) { echo "<tr><th>" , implode("</th><th>", array_keys($row)) , "</th></tr>"; }
echo "<tr><td>" , implode("</td><td>", $row) , "</td><tr>"
}
echo "</table>";
}
It looks like you are returning a single row of your results with this bit of the function:
foreach ($rows as $resultData)
{
return $resultData;
}
You should just return the whole thing instead.
while($row = $result->fetch_assoc())
{
$rows[] = $row;
}
return $rows;
return inside foreach() iteration means stop right after first iteration. Therefore you will be always getting only the first result.
You'd better write this as:
public function grabResults($table, $field, $id)
{
$result = $this->db->mysqli->query("SELECT * FROM $table WHERE $field = $id");
$rows = array();
while($row = $result->fetch_assoc()) {
$rows[] = $row;
}
return $rows;
}
I have data here:
Array
(
[1] => Array
(
[SiteID] => 1
[OwnerAID] => 1
)
[3] => Array
(
[SiteID] => 3
[OwnerAID] => 1
)
[6] => Array
(
[SiteID] => 6
[OwnerAID] => 2
)
)
Now, I need to group the OwnerAID into two categories: the first one is OwnerAID owning only one SiteID and the second one is OwnerAID owning more than 1 SiteID.
I've tried to make a program and do some research, but the output of my code is wrong.
Please see my code:
public function groupIndividualAndAggregateSites()
{
$arrays = array();
foreach($this->combined as $key => $key_value)
{
$SiteID = "";
if ($SiteID == "") {
$SiteID=array($key_value['SiteID']); }
else {
$SiteID = array_merge((array)$SiteID, (array)$key_value['SiteID']);
$SiteID = array_unique($SiteID);
}
} if ($SiteID != "") {
$arrays = array('AID'=>$key_value['AID'], 'Sites' => $SiteID);
}
print_r($arrays);
}
The result should be shown like this:
Array(
[1] => Array
( [Sites] => Array ([0] => 1, [1] => 3)))
Array(
[2] => Array
( [Sites] => Array ([0] => [6]))
What you should go for is array:
$owners = array(
owner_1 => SiteID1, // int Only one site
owner_2 => array (SiteID2,SiteID3), // array Multiple sites
);
and later use the array $owners like:
echo (is_array($owners[$owner_1]) ? 'Has multiple sites' : 'has one site';
Thats the basic idea of small memory footprint.
Example, not tested.
public function groupIndividualAndAggregateSites() {
$owners = array();
foreach($this->combined as $key => $value) {
$owner_id = $value['OwnerAID'];
$site_id = $value['SiteID'];
if(array_key_exists($owner_id,$owners)) {
// He has one or more sites already?
if(is_array($owners[$owner_id]){
array_push($owners[$owner_id],$site_id);
} else {
// User already has one site. Lets make an array instead and add old and new siteID
$old_site_id = $owners[$owner_id];
$owners[$owner_id] = array($old_site_id,$owner_id);
}
} else {
$owners[$owner_id] = $site_id;
}
return $owners;
}
All you need to do is loop over your initial array, appending each OwnerAID to the relevant output subarray as determined by the SiteID:
$output = array(1=>array(), 2=>array());
foreach ($original as $v) $output[$v['SiteID'] == 1 ? 1 : 2][] = $v['OwnerAID'];
Here I am using the following features:
array() initialisation function;
foreach control structure;
ternary operator;
$array[$key] addressing;
$array[] pushing.