I am creating a questionnaire for a client that requires the questions to be organized by 3 layers of levels. I've successfully created the U.I. however I've been trying for the last 3 hours to pull data from a database in such a way that everything loads in the right place. The database is organized like so by the client so i have no control over it:
id description parentId about
1 Level 1 0 This is the top level or in my case tab1
2 Level 2 0 This is the next tab in the top level
3 Level 1a 1 This is the first category under tab1
4 Level 1b 1 This is the next category under tab1
5 Level 1a1 3 This is the content under the first category of tab1
So anything with a parentId of 0 is the top level and will contain anything of the second level with the parentId of 1 and so on. Confusing yes, I can barely make sense of this but this is how I've been told to do it.
What approach would be the best way to execute something like this? An example from another question I'm using as a reference is attached below (although not working)
foreach (mysql_query("SELECT * FROM pB_test ORDER BY id ASC") as $row) {
$menuitem = array_merge(array(), $row);
$menuLookup[$menuitem['id']] = $menuitem;
if ($menuitem['parent'] == null) {
$menuitem['path'] = "/" . $menuitem['name'];
$menu[] = $menuitem[];
} else {
$parent = $menuLookup[$menuitem['parent']];
$menuitem['path'] = $parent['path'] . "/" . $menuitem['name'];
$parent['menu'][] = $menuitem;
}
}
Any help would be greatly appreciated. Cheers
If you have exactly 3 levels, then you can try this:
http://sqlfiddle.com/#!2/70e96/16
(
SELECT 1 AS lvl,
top_level.description AS o1, top_level.id AS id1,
NULL AS o2, NULL AS id2,
NULL AS o3, NULL AS id3,
top_level.*
FROM node AS top_level
WHERE top_level.parentId = 0
)UNION ALL(
SELECT 2 AS lvl,
top_level.description AS o1, top_level.id AS id1,
category_level.description AS o2, category_level.id AS id2,
NULL AS o3, NULL AS id3,
category_level.*
FROM node AS top_level
INNER JOIN node AS category_level ON category_level.parentId = top_level.id
WHERE top_level.parentId = 0
)UNION ALL(
SELECT 3 AS lvl,
top_level.description AS o1, top_level.id AS id1,
category_level.description AS o2, category_level.id AS id2,
last_level.description AS o3, last_level.id AS id3,
last_level.*
FROM node AS top_level
INNER JOIN node AS category_level ON category_level.parentId = top_level.id
INNER JOIN node AS last_level ON last_level.parentId = category_level.id
WHERE top_level.parentId = 0
)
ORDER BY o1,o2,o3;
I added a lvl field to the selects, different value for each level. Also added o1,o2,o3 for ordering nested levels nicely, of course you may have another needs. You could process all rows in PHP, for example split them into 3 arrays (one for each level), or maybe create a lookup table by id, etc.
It might be worth doing this in PHP, as opposed to SQL if you're working with an external database. I haven't benchmarked the following, so try with your data and see if performance is problematic or not.
You can choose yourself what to do with orphaned records (which reference parentIDs that don't exist anymore).
Ordering in PHP like this requires that you have all of your data beforehand, so use something like PDO's fetchAll(PDO::FETCH_ASSOC) method, which should result in something like this:
$data_from_database = array(
array("id" => 1, "parentId" => 0, "description" => "Level 1"),
array("id" => 2, "parentId" => 1, "description" => "Level 1a"),
array("id" => 3, "parentId" => 1, "description" => "Level 1b"),
array("id" => 4, "parentId" => 0, "description" => "Level 2"),
array("id" => 5, "parentId" => 2, "description" => "Level 1a1"),
array("id" => 6, "parentId" => 5, "description" => "Level 1a11a"),
array("id" => 7, "parentId" => 5, "description" => "Level 1a11b"),
array("id" => 8, "parentId" => 9, "description" => "Level 3"),
);
First off, you'll want to have the primary key (ID) as the array's keys. The following also adds the keys "children" and "is_orphan" to every record.
$data_by_id = array();
foreach($data_from_database as $row)
$data_by_id[$row["id"]] = $row + array(
"children" => array(),
"is_orphan" => false
);
This will look something like this:
$data_from_database = array(
1 => array("id" => 1, "parentId" => 0, "description" => "Level 1",
"children" => array(), "is_orphan" => false),
...
);
Now, it gets tricky: we'll loop through the array and add references.
foreach($data_by_id as &$row)
{
if($row["parentId"] > 0)
{
if(isset($data_by_id[$row["parentId"]]))
$data_by_id[$row["parentId"]]["children"][] = &$row;
else
$row["is_orphan"] = true;
}
}
unset($row); // Clear reference (important).
The last step is to clean up the 'root' of the array. It'll contain references to duplicate rows.
foreach($data_by_id as $id => $row)
{
// If you use this option, you'll remove
// orphaned records.
#if($row["parentId"] > 0)
# unset($data_by_id[$id]);
// Use this to keep orphans:
if($row["parentId"] > 0 AND !$row["is_orphan"])
unset($data_by_id[$id]);
}
Use print_r($data_by_id) after every step to see what happens.
If this proves to be a time consuming operation, try to build up the tree by only doing SELECT id, parentId FROM ... and then later fetching the metadata such as description. You could also store the result in Memcache or serialized into a database.
i also had the same kind of problem but after lot of googling and stackoverflowing :-)
i found my answer....
Here is my way of coding.
function showComments($parent = 0)
{
$commentQuery = "SELECT * FROM comment WHERE parent = ".mysql_real_escape_string($parentId);
$commentResult = mysql_query($commentQuery)
while ($row = mysql_fetch_array($commentResult))
{
echo '[Table containing comment data]';
showComments($row['commentID']);
}
}
showComments();
Related
I was wondering if anybody can help me out/point me in the right direction.
I have a database with a table that includes incremental id, name, unique id, and the parent uuid of fictional characters.
The table shows the following people...
John (Parent not listed)
Steve (Parent listed as John)
Mark (Parent listed as John)
Kevin (Parent listed as Steve)
Adam (Parent listed as Mark)
**ID, NAME, UUID, PARENT_UUID**
1, John, 0001, none
2, Steve, 0002, 0001
3, Mark, 0003, 0001
4, Kevin, 0004, 0002
5, Adam, 0005, 0003
So in this example, John has 2 sons, Steve and Mark... each of whome have a son, Kevin and Adam.
What I want to do, is (on an already made profile page) show the number of family members.
So going to Johns page, I would see "John (4 Descendents)", and if I went to Marks page, I'd see "Mark (1 Descendents)"
Allowing me to list how many family members, in lower generations are found in the table.
I am able to print a list of all family members... using:
$sql = "SELECT * FROM users";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo $row["uuid"]. "<br>";
}
}
But now I want to find out for each $row['uuid'] how many descendents each entry has.
I tried doing something like the following:
$sql = "SELECT * FROM users";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$sql = "SELECT * FROM users WHERE parent_uuid = '".$row['uuid']."'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$numOfDescendents = $result->num_rows;
}
}
}
But then I realised that not only does this not work (it grabs the number of the first row and stops), that using this logic, if I have a family with 20+ generations, I'm going to need to have 20+ while loops nested within each other, checking each generation.
So I'm wondering, is this even possible?
There's probably an easy way, but after 3/4 days of headaches and frustration I'm finally asking for help =)
My end goal is to have a php file that checks through the table for all users, and totals their descendents by counting how many people in the table use their uuid as a parent_uuid... as in the example at the start... John would have 4, Mark would have 1, Steve would have 1, Kevin would have 0, and Adam would have 0.
Thanks in advance for any guidance =)
You can use a recursive function to solve this. Note - I am passing the entire input to the recursive array which I do not recommend. If you are using a class set the input as a property of the class or you can use global which is also not recommended.
$input = [
['id' => 1, 'name' => 'John', 'UUID' => 0001, 'PARENT_UUID' => null],
['id' => 2, 'name' => 'Steve', 'UUID' => 0002, 'PARENT_UUID' => 0001],
['id' => 3, 'name' => 'Mark', 'UUID' => 0003, 'PARENT_UUID' => 0001],
['id' => 4, 'name' => 'Kevin', 'UUID' => 0004, 'PARENT_UUID' => 0002],
['id' => 5, 'name' => 'Adam', 'UUID' => 0005, 'PARENT_UUID' => 0003],
];
function getDescendents($uuid, $input, $count = 0, $descendants = [])
{
foreach ($input as $user) {
if ($user['PARENT_UUID'] === $uuid) {
$count++;
$descendants[] = $user['UUID'];
}
}
if (!empty($descendants)) {
$new_uuid = array_shift($descendants);
return getDescendents($new_uuid, $input, $count, $descendants);
}
return $count;
}
$result = [];
foreach ($input as $user) {
$result[$user['name']] = getDescendents($user['UUID'], $input);
}
echo(json_encode($result));
Output -
{"John":4,"Steve":1,"Mark":1,"Kevin":0,"Adam":0}
The key in the above object is the user's name and the value is the number of descendants.
I am trying to merge two results of two queries in MYSQL using PHP, but I am puzzled how to do it! I am using PDO. I am programming for a hobby and am trying to make a to do list app just like a Trello board. However, I just can't figure out how to merge two results from different tables in a database.
The idea is as follows:
I have a table called 'task_lists' with the content:
'list_id => 1, list_name = 'listOne'
'list_id => 2, list_name = 'listTwo'
And a table called 'tasks':
task_id => 1, list_id => 1, task_name => 'taskOfListOne', duration => 5, is_done => 1
task_id => 2, list_id => 1, task_name => 'anotherTaskOfListOne', duration => 5, is_done => 1
task_id => 3, list_id => 2, task_name => 'taskOfListTwo', duration => 10, is_done => 0
And I am trying to create an array that is merged between the two results as something like:
(I know this is a rough picture of how the array is supposed to look like)
$result = [array]
[list_id] = 1, [list_name] = 'listOne' =>
[array][list_id] = 1, ['task_name] = taskOfListOne,[duration] = 5, ['is_done'] => 1
[array][list_id] = 1, ['task_name] = anotherTaskOfListOne,[duration] = 5, ['is_done'] => 1
[list_id] = 2, [list_name] = 'listTwo' =>
[array][list_id] = 2, ['task_name] = taskOfListTwo,[duration] = 5, ['is_done'] => 1
Is this even possible? I have tried a Union sql query and methods like nested foreach statements, but none of them worked for me. Am I missing something here?
PS: Sorry for my bad english.
Have you tried a left join?
SELECT TL.`list_id`, TL.`list_name`, T.`task_name`, T.`duration`
FROM task_lists AS TL
LEFT JOIN tasks as T ON TL.`list_id` = T.`list_id`
And then in PHP you build the array in the format you want.
Later edit:
Simple PHP example to parse SQL data as you asked (to remove duplicated info):
<?php
// $mysql_rows -> here is your query result, fetched as associative array
$filtered_array = array();
foreach ($mysql_rows as $row){
// Initiate record if is not already initiated
if (!isset($filtered_array[ $row['list_id'] ])){
$filtered_array[ $row['list_id'] ] = array(
'list_id' => $row['list_id'],
'list_name' => $row['list_name'],
'tasks' => array()
);
}
// Add tasks
$filtered_array[ $row['list_id'] ]['tasks'][] = array(
'task_name' => $row['task_name'],
'duration' => $row['duration'],
'is_done ' => $row['is_done ']
);
}
// Optional: if you want to remove list_id from $filtered_array key names, uncomment the next line
// $filtered_array = array_values($filtered_array);
?>
I am trying to LIMIT a Query which is already been selected. I know I can directly do it in my query select, but due to some logics of the code, if I do that way I have to run the query selection twice which (I believe) will increase the computational time!
So, what I am trying is this:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
then, I need to re-select a sub-selection of the $query1 for my Pagination. What I am trying to do is something like this;
$query2 = LIMIT($query1, $limit_bigin, $limit_end);
where $limit_bigin, $limit_end provide the LIMITING range (start and end respectively).
Could someone please let me know how I could do this?
P.S. I know I can do it directly by:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
$query2 = $mysqli->query("SELECT * FROM tableName WHERE someConditions LIMIT $limit_bigin, $limit_end");
But this is running the query twice and slows down the process (I must run the first query without limits due to some logics of the program)
EDIT 1
As per the answers I tried using array_slice in PHP. BUT, since Query is an object it doesn't give the results that was expected. A NULL is resulted form
array_slice($query1, $start, $length, FALSE)
If you have already carried out the query and the result set has been returned to your PHP, you can not then LIMIT it. As you state, then running a second SQL execution of a subpart of the same query is wasteful and repetative.
Don't
Repeat
Yourself.
DRY.
As I said above, repetition causes issues with maintainability as you can easily forget about repetition, tweaking one SQL and forgetting to tweak the other.
Don't
Repeat
Yourself.
DRY.
Use PHP instead
Once you have executed the query, the result set is then passed back to PHP.
So assuming you have a $var with the contents of you SQL query, then you simply need to select the valid rows from the $var, not from the database [again].
You can do this using PHP numerous Array functions. Particularly array_slice().
So;
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
Now, to select the second page, say for example rows 10 to 20:
$query2 = array_slice($query1, (10-1), 10 );
This wil then "slice" the part of the array you want. Remember that the array counts will start at zero so to grab row 10 (of an index starting at 1, Typical of a MySQL Auto Increment Primary Key), then you will need to select X number of rows from row (10-1) .
Please also read the manual for PHP array_slice().
Further
As referenced in comments, there is no guarentee that your SQL will return the same values each time in the same order, so it is highly recommended to order your results:
$query1 = $mysqli->query("SELECT * FROM tableName
WHERE someconditions ORDER BY primary_key_column");
Example Data:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions ORDER BY id");
/***
$query1 = array {
0 => array { 'id' => 1, 'user' => "Jim", 'colour' => "Blue" },
1 => array { 'id' => 2, 'user' => "Bob", 'colour' => "Green" },
2 => array { 'id' => 3, 'user' => "Tom", 'colour' => "Orange" },
3 => array { 'id' => 4, 'user' => "Tim", 'colour' => "Yellow" },
4 => array { 'id' => 5, 'user' => "Lee", 'colour' => "Red" },
5 => array { 'id' => 6, 'user' => "Amy", 'colour' => "Black" }
}
***/
$page = 2;
$size = 3; // number per page.
$start = ($page - 1) * $size; //page number x number per page.
// Select the second page of 3 results.
$query2 = array_slice($query1, $start, $size , FALSE);
/***
$query2 = array {
0 => array { 'id' => 4, 'user' => "Tim", 'colour' => "Yellow" },
1 => array { 'id' => 5, 'user' => "Lee", 'colour' => "Red" },
2 => array { 'id' => 6, 'user' => "Amy", 'colour' => "Black" }
}
***/
You can then use these in a foreach or other standard array manipulation technique.
I want to insert multiple row in my table, the data for this query is fetched from front end. One column in this query is not passed from front end, its value is determined at query execution time from another table. I can put these array in loop & perform one SELECT query & one INSERT query for each item, but I want to avoid it, thus I created one large INSERT query! Till here everything fine, but now that one value should be from another table!
I can use INSERT...SELECT method
https://dev.mysql.com/doc/refman/5.6/en/insert-select.html
It allows insertion of multiple rows, all rows matched are inserted & I can pass a dummy column value in SELECT part to customise the query. But now problem is these 'faked columns' are same for all rows, they should me in loop, different for each loop! how to achieve that?
Scenario
$products = array(
"1" => array(
"product_name" => "apple",
"units" => "1",
),
"2" => array(
"product_name" => "mango",
"units" => "3",
),
);
Suppose this is the array I get from front end, each key is product id which contains other description for product in cart. Now I'll insert this in Orders table but price is missing, which I have to fetch from Products table! For which I can perform select using product id.
Similar Question:
This answer uses faked columns:
MYSQL INSERT SELECT problem
Copied from accepted answer:
INSERT INTO website (url,fid) SELECT $site,id FROM users WHERE name = $user
Here $site will be same for all inserted records, I want it different for each record as per my array!
I research on this topic but can't find desired answer :(
Try this code if it helps,
<?php
$products = array(
"1" => array(
"product_name" => "apple",
"units" => "1",
),
"3" => array(
"product_name" => "mango",
"units" => "3",
),
"7" => array(
"product_name" => "mango",
"units" => "3",
),
);
$result = $conn->query("SELECT product_id, product_price FROM product_table WHERE product_id IN (".implode(',', array_keys($products)).")");
while($row = $result->fetch_assoc()) {
$prod_price[$row['product_id']] = $row['product_price'];
}
$qry = array();
foreach ($products as $key => $value) {
$qry[] = "('".$value['product_name']."', '".$value['units']."', '".$prod_price[ $key]."')";
}
$insert = "INSERT INTO orders (`product_name`, `units`, `product_price`) VALUES ".implode(', ', $qry);
I come up with this code based on my understanding of your question. Let me know if it works.
This approach just hits the DB twice only.
I'm working with a dataset coming back from a very complex view with multiple subselects and joins against several tables in a very large and convoluted database.
Each record has a structure like this:
MainValue = XXTS10, qtyPlaceholder1, qtyPlaceholder2, qtyPlaceholder3..., actualQty = 3, qtyPlaceholderKey = 1, color = blue.
MainValue = XXTS10, qtyPlaceholder1, qtyPlaceholder2, qtyPlaceholder3..., actualQty = 10, qtyPlaceholderKey = 3, color = blue.
MainValue = XXTS10, qtyPlaceholder1, qtyPlaceholder2, qtyPlaceholder3..., actualQty = 9, qtyPlaceholderKey = 2, color = blue.
So for each color and MainValue values, there are multiple records. I need to set the value of each qtyPlaceholder based on the actualQty where the qtyPlaceholderKey will tell me what value to put in each and derive only one record out of many so that the final single record looks like this:
MainValue = XXTS10, qtyPlaceholder1 = 3, qtyPlaceholder2 = 9, qtyPlaceholder3 = 10, color = blue.
I know I've done this hundreds of times over the years, but I'm just having mental block creating the proper looping structure and conditionals to create a single record out of many with the values mapped to the placeholders correctly. Trying to accomplish this in PHP, but it may be a good idea to reexamine the view and see if it can be adjusted, but I really don't want to go down that path if I can help it.
Any suggestions?
You can do this in SQL using conditional aggregation. Here is a select form of the query:
select MainValue,
max(case when qtyPlaceholderKey = 1 then actualQty end) as qtyPlaceholder1,
max(case when qtyPlaceholderKey = 2 then actualQty end) as qtyPlaceholder2,
max(case when qtyPlaceholderKey = 3 then actualQty end) as qtyPlaceholder3,
color
from complexview v
group by MainValue, color;
Loop through your array and create a new array indexed by
MainValue
color
qtyPlaceholderKey with a value of actualValue
Then 'flatten' the new array by looping through the new array and assigning key value pairs for MainValue, color and all qtyPlaceholderKeys and their corresponding actualValues.
$dbrows = array(
array(
'MainValue' => 'XXTS10',
'actualQty' => 3,
'qtyPlaceholderKey' => 1,
'color' => 'blue',
),
array(
'MainValue' => 'XXTS10',
'actualQty' => 9,
'qtyPlaceholderKey' => 2,
'color' => 'blue',
),
array(
'MainValue' => 'XXTS10',
'actualQty' => 10,
'qtyPlaceholderKey' => 3,
'color' => 'blue',
),
);
$values = array();
foreach($dbrows as $r) {
$values[$r['MainValue']][$r['color']][$r['qtyPlaceholderKey']] = $r['actualQty'];
}
$result = array();
foreach($values as $mainValue => $colorValues) {
foreach($colorValues as $color => $qtyPlaceholderValues) {
$row = array('MainValue' => $mainValue, 'color' => $color);
foreach($qtyPlaceholderValues as $qtyPlaceholderKey => $actualQty) {
$row["qtyPlaceholderKey$qtyPlaceholderKey"] = $actualQty;
}
$result[] = $row;
}
}
print_r($result);
Demo