Parent Child Relationships PHP/MYSQL - php

I have a table like this:
id
name
parent_id
I then want to select certain rows based on their id, so something like this:
SELECT *
FROM TABLE
WHERE id IN ('1', '5', '8', '9', '35')
I want to, from this query, also show the parent/child relationship, like:
id parent
-----------
1 0
5 1
8 0
9 8
35 9
So the final output would look something like this:
1
--5
8
--9
----35
Do I do this outside of mysql, i have tried using arrays, but can't figure it out, or
Do I do it inside MYSQL, which i don't know how to do that either.

Here is what I was able to come with which seems to be working great.
PS-Sorry about the formatting, can't figure it out :( (fixed?)
I grab my parent_id and id from MYSQL and put it into an arraly where the array keys are the id's and the values are the parents, so with in the while loop for mysql, something like this: $testarray[$id] = $parent_id;
Then I run it through the functions below, and it orders it just how I need it.
function retrieveSubTree($parent, $myarray) {
$tempArray = $myarray;
$array = array();
//now we have our top level parent, lets put its children into an array, yea!
while ($child = array_search($parent, $tempArray)) {
unset($tempArray[$child]);
//now lets get all this guys children
if (in_array($child, $tempArray)) {
$array[$child] = retrieveSubTree($child, $tempArray);
} else {
$array[$child] = true;
}
}//end while
return (!empty($array)) ? $array : false;
}
function retrieveTree($myarray) {
$array = array();
$counter = 0;
foreach ($myarray as $key => $value) {
$child = $key;
$parent = $value;
//if this child is a parent of somebody else
if (in_array($child, $myarray) && $parent != '0') {
while ($myarray[$parent] != '' && $myarray[$parent] != '0') {
$newparent = $myarray[$parent];
$parent = $newparent;
}
if (!array_key_exists($parent, $array)) {
$array[$parent] = retrieveSubTree($parent, $myarray);
}
} else {
//now make sure they don't appear as some child
if (!array_key_exists($parent, $myarray)) {
//see if it is a parent of anybody
if (in_array($child, $myarray)) {
$array[$child] = retrieveSubTree($child, $myarray);
} else {
$array[$child] = true;
}
}//end if array key
}//end initial in array
}//end foreach
return (!empty($array) ? $array : false);
}
$test = array(
'1'=>'15',
'2'=>'1',
'3'=>'1',
'4'=>'0',
'5'=>'0',
'6'=>'4',
'7'=>'6',
'8'=>'7',
'9'=>'2',
'10'=>'9'
);
print_r(retrieveTree($test));

Without changing your table structure, this requires recursion, which MySQL does not support. You'll have to do it elsewhere. You can write a recursive function in PHP to use, for example, breadth-first search to build your array. Here it looks like you are using parent_id of 0 to denote a top-level object. You can search over your results, and add to your array every object whose parent is zero, which will give you an array with 1 and 8. Then you can recurse: find all the results with a parent of 1, and add that as a subarray to 1; then find all the results with a parent of 8 and add those as a subarray of 8. Continue doing this for each level until you've run out of results.
As other posters pointed out, you can do this natively in MySQL if you can change the table structure.

Related

Check if multiple dates are the same in foreach

I have an "Events" table in MySQL :
EventsDate('id','event','start_date','end_date')
I'd like to check if multiple events have the same start date to show it differently in my HTML template.
My SQL request is :
SELECT * FROM EVENTSDATE where event='$id' and start_date>='$today' order by start_date asc
Now my foreach :
foreach ($upcomingDates as $value) { //$upcoming is the array with my sql request
}
How can I say : "if you find two rows with the same start_date, echo something"
I have a slightly different approach.
// Array to contain all values
$container = array();
// Loop through your existing array
foreach ($upcomingDates as $key => $value) {
// Check if the value is already in the container array
// If this is the case, its a duplicate.
if (array_key_exists($value['start_date'], $container)) {
$container[$value['start_date']]++;
echo $value.' is a duplicate with key '.$key;
}
// Add each value to the array
$container[$value['start_date']] = 1;
}
Another method is to use array_count_values()
foreach(array_count_values($upcomingDates) as $value => $c) {
if ($c > 1) {
echo $value.' is a duplicate';
}
}
Note that the second option won't work if your $upcomingDates is an array of arrays.
You can make an empty array before the for loop, and add each value in as a key. Then, on each iteration you can check that array for the key, like so:
$values = [];
foreach ($upcomingDates as $value) { //$upcoming is the array with my sql request
if(isset($values[$value])) //duplicate value found
//do something here
$values[$value] = 1;
}
Since you're ordering your events by start_date:
for ($i = 0, $length = count($upcomingDates); $i < $length; $i++) {
$date = $upcomingDates[$i];
if (isset($upcomingDates[$i + 1]) &&
$upcomingDates[$i + 1]['start_date'] == $date['state_date']) {
echo 'this and the next date are equal';
}
}
Try out GROUP BY
look here: GROUP BY
if you want to find duplicates, then you can directly get it from database
ex.
SELECT * FROM EVENTSDATE where event='$id' and start_date>='$today' GROUP BY start_date having count(start_date) > 1 order by start_date asc
or you can find duplicates from resulting array
return only duplicated entries from an array

Distribute options uniquely algorithm

I have a 2 dimensional array. Each subarray consists out of a number of options. I am trying to write a script which picks one of these options for each row. The chosen options have to be unique. An example:
$array = array(
1 => array(3,1),
2 => array(3),
3 => array(1,5,3),
);
With a solution:
$array = array(
1 => 1,
2 => 3,
3 => 5,
);
I have finished the script, but i am not sure if it is correct. This is my script. The description of what i am doing is in the comments.
function pickUnique($array){
//Count how many times each option appears
$counts = array();
foreach($array AS $options){
if(is_array($options)){
foreach($options AS $value){
//Add count
$counts[$value] = (isset($counts[$value]) ? $counts[$value]+1 : 1);
}
}
}
asort($counts);
$didChange = false;
foreach($counts AS $value => $count){
//Check one possible value, starting with the ones that appear the least amount of times
$key = null;
$scoreMin = null;
//Search each row with the value in it. Pick the row which has the lowest amount of other options
foreach($array AS $array_key => $array_options){
if(is_array($array_options)){
if(in_array($value,$array_options)){
//Get score
$score = 0;
$score = count($array_options)-1;
if($scoreMin === null OR ($score < $scoreMin)){
//Store row with lowest amount of other options
$scoreMin = $score;
$key = $array_key;
}
}
}
}
if($key !== null){
//Store that we changed something while running this function
$didChange = true;
//Change to count array. This holds how many times each value appears.
foreach($array[$key] AS $delValue){
$counts[$delValue]--;
}
//Remove chosen value from other arrays
foreach($array AS $rowKey => $options){
if(is_array($options)){
if(in_array($value,$options)){
unset($array[$rowKey][array_search($value,$options)]);
}
}
}
//Set value
$array[$key] = $value;
}
}
//validate, check if every row is an integer
$success = true;
foreach($array AS $row){
if(is_array($row)){
$success = false;
break;
}
}
if(!$success AND $didChange){
//Not done, but we made changes this run so lets try again
$array = pickUnique($array);
}elseif(!$success){
//Not done and nothing happened this function run, give up.
return null;
}else{
//Done
return $array;
}
}
My main problem is is that i have no way to verify if this is correct. Next to that i also am quite sure this problem has been solved a lot of times, but i cannot seem to find it. The only way i can verificate this (as far as i know) is by running the code a lot of times for random arrays and stopping when it encounters an insolvable array. Then i check that manually. So far the results are good, but this way of verication is ofcourse not correct.
I hope somebody can help me, either with the solution, the name of the problem or the verification method.

Removing an item from an array during a foreach loop

I have the following foreach being performed in PHP.
What I would like to do is instead of the $invalid_ids[] = $product_id; building and then looping around that, I would instead like to remove the entry from array that is being looped around as I'm looping around it..
For example:
If the current $product_id fails any of the test, delete the item from the $current_list array and proceed to the next iteration of the foreach loop.
I tried to do an unset($product_id) while the foreach loop header looked like this: foreach ($current_list as &$product_id) {, but the item item is still in the array.
Does anyone have any ideas on how I can go about doing this?
foreach ($current_list as $product_id) {
// Test 1 - Is the product still active?
// How to test? - Search for a product in the (only active) products table
$valid = $db->Execute("SELECT * FROM " . TABLE_PRODUCTS . " WHERE products_id = " . $product_id . " AND products_status = 1");
// Our line to check if this is okay.
if ($valid->RecordCount <= 0) { // We didn't find an active item.
$invalid_ids[] = $product_id;
}
// Test 2 - Is the product sold out?
if ($valid->fields['products_quantity'] <= 0 and STOCK_ALLOW_CHECKOUT == "false") { // We found a sold out item and it is not okay to checkout.
$invalid_ids[] = $product_id;
}
// Test 3 - Does the product have an image?
if (empty($valid->fields['products_image'])) { // Self explanatory.
$invalid_ids[] = $product_id;
}
}
$product_id isn't the actual data in the array, it's a copy of it. You would need to unset the item from $current_list.
I'm don't know how $current_list is stored, but something like unset($current_list['current_item'] would do the trick. You can use key to select the current_item key in the array.
A similar way of iterating the Array, where you can get the array key, from the PHP key docs...
while ($fruit_name = current($array)) {
if ($fruit_name == 'apple') {
echo key($array).'<br />';
}
next($array);
}
Untested, but something like this...
while ($product_id = current($current_list)) {
// Do your checks on $product_id, and if it needs deleting...
$keyToDelete = key($array);
unset($current_list[$keyToDelete]);
next($current_list);
}
I think this simple code may help you
let's say we have an array of integers and we want to remove all the items that are equal to "2" inside of the foreach loop
$array = [1,2,1,2,1,2,1];
foreach ($array as $key => $value)
{
if($value==2)
unset($array[$key]);
}
var_dump($array);
this shows the following result
array (size=4)
0 => int 1
2 => int 1
4 => int 1
6 => int 1

adding elements in an array day-wise from mysql database

How can I get the record values in the database to be sorted like this in an array. Supose I am adding the day no.
array
[0] => array=>'id'=>'26' 'date'=>'26'
[1] => array=>'id'=>'27' 'date'=>'27',
array=>'id'=>'28' 'date'=>'27',
array=>'id'=>'29' 'date'=>'27'
[2] => array=>'id'=>'30' 'date'=>'29'
[3] => array=>'id'=>'31' 'date'=>'31',
array=>'id'=>'32' 'date'=>'31',
array=>'id'=>'33' 'date'=>'31'
Basically, I want to add an array to the same index if the next id contains a record with the same date (day no) of the month. Otherwise add it normally.
Right now, My function is adding the rows of the record without sorting it in the format I want it to be in.
The reason I want it to be in this format is because, I need to run the foreach, and if 1 day contains 2 records, then it will append another <li> into my unordered list.
public function getArticles()
{
$sql = 'CALL getArticles()';
$articles = Array();
if (Model::getConnection()->multi_query($sql)) {
do {
if ($result = Model::getConnection()->store_result()) {
while ($row = $result->fetch_assoc()) {
array_push($articles,$row);
}
$result->free();
}
} while (Model::getConnection()->next_result());
}
return $articles;
}
I don't recognize what some of your code is doing, but I think this is the important part.
while ($row = $result->fetch_assoc()) {
if (!isset($articles[$row['date']])) {
$articles[$row['date']] = array();
}
$articles[$row['date']][] = $row;
}
The only difference will be that your array will be keyed on the date instead of incrementing from zero. If you really want it reindexed, you can do...
array_values($articles);
As savinger pointed out, there is alot of functionality in your code which I don't really know, so I've included comments to indicate whereabouts the process is:
// Start of your loop
// $value is what you're getting from the DB...
$array_search = array_search($value,$main_array);
if($array_search)
{
// If this value already exists, we need to add
// this value in +1 of its current value
$main_array[$array_search][] = $value + 1;
}
else
{
// Make a new array key and add in the value
$main_array[] = $value;
}
// End of your loop

How do I extract and display hierarchical data from my database?

I have two tables.
The chapters table has the columns id and name.
The chapters_chapter table has columns id, master_id, and slave_id.
Lets say that the chapters table has 7 records:
id name
1 test01
2 test02
3 test03
4 test04
5 test05
6 test06
7 test07
And in the chapters_chapters table I have these records:
id master_id slave_id
1 1 5
2 1 6
3 6 7
4 7 2
Given that data, how can I extract the hierarchy of that data so that it looks like this?
test01
test05
test06
test07
test02
test03
test04
So this was kind of a pain because of the fact that we had to have the hierarchy stored in the DB. Because of this, each item can have multiple children, and each child can have multiple parents.
This second part means we cannot simply loop through the list once and be done with it. We might have to insert an item in multiple places in the hierarchy. While you probably won't actually structure your data that way, the database schema you've described supports this scenario, so the code must support it too.
Here's a high-level version of the algorithm:
Query both tables
Create a map (array) of a parent (number) to its children (another array)
Create a set of items that are children (array of numbers)
Create a function that displays a single item, indenting it to the desired depth.
If that item has children, this function increases the depth by one, and calls itself recursively
Loop through all items that aren't children (root items).
Call the function for each of those items, with a desired depth of 0 (no indent).
Here's two hours work. Enjoy :)
Note that I stuck it within a <pre> block, so you might have to mess with how the indentation is done (output something other than two spaces, mess with the style of the divs, etc).
<?php
$con = mysql_connect("localhost", "test_user", "your_password");
if(!$con)
{
die("could not connect to DB: " . mysql_error());
}
mysql_select_db("your_db", $con);
// get chapters
$chapters = array();
$result = mysql_query("SELECT * FROM chapters");
while($row = mysql_fetch_array($result))
{
$id = $row["id"];
$name = $row["name"];
$chapters[$id] = $name;
}
// get chapters_chapters - We'll call it "parent/child" instead of "master/slave"
$parent_child_map = array();
$is_child = array();
$result = mysql_query("SELECT master_id, slave_id FROM chapters_chapters");
while($row = mysql_fetch_array($result))
{
$parent_id = $row["master_id"];
$child_id = $row["slave_id"];
$children = $parent_child_map[$parent_id];
if($children == null)
{
$children = array();
}
$children[] = $child_id;
$parent_child_map[$parent_id] = $children;
$is_child[$child_id] = true;
}
// display item hierarchically
$display_item_and_children = function($id, $name, $depth)
use ($chapters, $parent_child_map, &$display_item_and_children)
{
echo "<div><pre>";
// indent up to depth
for($i = 0; $i < $depth; $i++)
{
echo " ";
}
echo "id: " . $id
. " name: " . $name
. "</pre></div>";
// if there are children, display them recursively
$children = $parent_child_map[$id];
if($children != null)
{
foreach($children as $child_id)
{
$child_name = $chapters[$child_id];
$display_item_and_children($child_id, $child_name, $depth + 1);
}
}
};
// display all top-level items hierarchically
foreach($chapters as $id => $name)
{
// if it is a top-level item, display it
if($is_child[$id] != true)
{
$display_item_and_children($id, $name, 0);
}
}
mysql_close($con);
?>
And here's a screenshot:
The question becomes how complex you want your solution to be. I'd do it with the following pseudo code.
SELECT all the chapters
SELECT all the *chapters_chapters*
loop over the chapters to create an array chapter objects
loop over the `chapters_chapters* and create the relationships using the chapter objects
Essentially you're creating a link-list.

Categories