I am trying to make un-order list for parent child categories where if there is any child category than it will create another un-order list ( like indented text) so user can understand properly.
I have fetch sql but with foreach I don't understand how to set so where child category only will display under parent category by creating another un-order list under the parent category.
Here is my code
$query_cat = "SELECT * FROM ^categories";
$query = qa_db_query_sub($query_cat);
$catsid = qa_db_read_all_assoc($query);
echo '<UL>';
foreach ($catsid as $catid){
echo '<LI>'. $catid['title'].' '. $catid['categoryid'].'</LI>';
}
echo '</UL>';
So final result would be
First Category
Sub Category1
Second Category
EDIT:
After modified code with #vlcekmi3 answer https://stackoverflow.com/a/13451136/1053190 I am getting this result
Now how to exclude subcategory from parent list?
There's no really easy solution for this with your design. The most effective way would be to add column like order_in_list (and maybe depth_in_list).
They would be pre calculated in loop (pseudocode):
START TRANSACTION
UPDATE t1 SET order_in_list = 0 // Restart whole loop
$ids = array(0);
while $id = array_shift($ids){
$record = SELECT * FROM t1 WHERE id = $id // Get id details, order_in_list is important
$children = SELECT * FROM t1 WHERE parent_id = $id // get list of all childs
// If it's root element, start indexing from 0
$root_order = ($record ? $record->order_in_list : 1)
$child_no = count($children) // How many child will be adding
// No children, nothing to do:
if $child_no < 1{
continue;
}
append_to_array($ids, $children) // Store ids to process
// Shift all later records, we'll be creating gap in order_in_list 1,2,3,4,5
// To 1,2,5,6,7 to insert items on places 3,4
UPDATE t1 SET order_in_list = (order_in_list + $child_no)
WHERE order_in_list > $record->order_in_list
// Okay, set IDs for direct children
foreach( $children as $child){
UPDATE t1 SET order_in_list = $root_order, depth_in_list = $record->depth_in_list+1
WHERE id = $child->id
$root_order++;
}
}
COMMIT
This way you'll get records like:
First category, 1, 1
Second category 3, 1
Sub category, 2, 2
Which you could display with simple loop:
$last_depth = 0;
foreach( (SELECT * FROM t1 ORDER by `order_in_list`) as $row){
if( $last_detph > $row['depth_in_list'])){
// Close level </ul>
} else if($last_detph < $row['depth_in_list']){
// Opening level <ul>
} else {
// The same depth
}
$last_depth = $row['depth_in_list'];
}
Without modifying database
It would be probably most effective to build two arrays containing root elements and all elements:
$root_elements = array();
$all_elements = array();
foreach( (SELECT * FROM t1) as $row){
// Store details into all_elements, note that entry may have already be created when
// processing child node
if( isset( $all_elements[$row['id']])){
// set details
} else {
$all_elements[$row['id']] = $row;
$all_elements[$row['id']]['children'] = array(); // Array of child elements
}
if( $row['parent_id'] == NULL){
$all_elements[] = $row['id']; // Add row element
} else {
if( isset( $all_elements[ $row[ 'parent_id']])){
$all_elements[ $row[ 'parent_id']]['children'][] = $row['id'];
} else {
// Create new record:
$all_elements[ $row[ 'parent_id']] = array();
$all_elements[ $row[ 'parent_id']]['children'] = array($row['id']);
}
}
}
And then write it as:
foreach( $root_elements as $element_id){
write_recursive( $all_elements[ $element_id]);
}
// And display
function write_recursive( $element)
{
echo '<ul>...';
if( count( $element['children'])){
foreach( $element['children'] as $child){
write_recursive( $all_elements[ $child]);
}
}
echo '</ul>';
}
You better create class for that (to replace using global variables), but you should have a solid way to do this. Anyway try avoid using this with large number of records (I wouldn't go past 2000-5000 menu entries), try to at least cache it.
Note: solutions are oriented towards minimal number of requests on database when displaying list.
you can use complicated query or something like this
foreach ($catsid as $catid) {
...
$subquery_cat = "SELECT * FROM ^categories WHERE parentid='".$catid['categoryid']."'";
$query = qa_db_query_sub($subquery_cat);
$subcatsid = qa_db_read_all_assoc($query);
// wrap into html
...
}
Related
I have the following query which pulls from 3 tables. I'll end up with 1 family per row but with multiple children for each. I want to be able to show all children ages within the family row. I thought about opening another connection/query, but figured there is a smarter way.
Query:
SELECT
families.*, job.*, children.*, families.first_name AS fam_firstname, children.first_name AS child_firstname
FROM job
LEFT OUTER JOIN families ON job.fam_id = families.fam_id
LEFT OUTER JOIN children ON families.fam_id = children.fam_id
WHERE
job.published = 2
GROUP BY job.job_id
ORDER BY job.created_on DESC
Loop:
if ($result = $mysqli->query($query)) {
$from = new DateTime($row['dob']);
$to = new DateTime('today');
while ($row = $result->fetch_assoc()) {
echo '<tr>';
echo '<td>' .$row['fam_firstname']. '</td>';
echo '<td>' .$row['last_name'].'</td>';
/* Looking to list all children ages. Separate by comma or break */
echo '<td>' . $from->diff($to)->y .'</td>';
echo '</tr>';
}
$result->free();
}
Desired Output:
Family First Name | Family Last Name | Child 1 Age, Child 2 Age
You need to use the mysql group_concat function to achieve this:
SELECT
families.*, group_concat(children.age)
FROM job
LEFT OUTER JOIN families ON job.fam_id = families.fam_id
LEFT OUTER JOIN children ON families.fam_id = children.fam_id
WHERE
job.published = 2
group by families.fam_id
ORDER BY job.created_on DESC
Follow this question: Nested Array from multiple tables.
Refer to the second option in the question, that explains how you subtract your data from the JOIN query.
P.S.
I'ts a question I've asked myself, with an implementation with what your'e trying to do here. If you need more lead on how to implement it here, ask in comments...
Here is a way to implement it in your code (notice you should order your JOIN query by "fam_firstname", for this code to work for you):
/* init temp vars to save current family's data */
$current = null;
$fam_firstname = null;
$children = array();
while ($row = mysql_fetch_assoc($result))
{
/*
if the current id is different from the previous id:
you've got to a new family.
print the previous family (if such exists),
and create a new one
*/
if ($row['fam_firstname'] != $fam_firstname )
{
// in the first iteration,
// current (previous family) is null,
// don't print it
if ( !is_null($current) )
{
$current['children'] = $children;
/*
Here you print the whole line
I'm just dumping it all here, but you can print
it more nicer...
*/
var_dump($current);
$current = null;
$fam_firstname = null;
$children = array();
}
// create a new family
$current = array();
$current['fam_firstname'] = $row['fam_firstname'];
/*
Add more columns value here...
*/
// set current as previous id
$fam_firstname = $current['fam_firstname'];
}
// you always add the phone-number
// to the current phone-number list
$children[] = $row['child_firstname'] . " is " . $row['child_age'] . " years old";
}
}
// don't forget to print the last family (saved in "current")
if (!is_null($current))
/*
Here you print the whole line
I'm just dumping it all here, but you can print
it more nicer...
*/
var_dump($current);
Experts is there any option to automatically increase +1 in category_level
for ex: $cond_cat=" where category_level='1' and ID='$sleads' "; than $cond_cat=" where category_level='2' and parent_id ='$sleads' ";
my code is given below.
<?php
include('classes/global.php');
$CT=new Product();
$AdminObj=new Admin();
$sleads=$_REQUEST['sleads'];
$cond_cat=" where category_level='1' and ID='$sleads' ";
$catResult_cond=$CT->getCategory($cond_cat);
$cond_cat=" where category_level='2' and parent_id ='$sleads' ";
$subCatResult_cond=$CT->getCategory($cond_cat);
?>
You may want to try to recursively get subcategories as a child of the subcategory you are into.
<?php
function getSubCategories($level) {
// you should obviously get the $sleads variable in here, for this to work.
$cond_cat = "where category_level='%d' and ID='$sleads' ";
$subcategories = $CT->getCategory(sprintf($cond_cat, $level));
if (count($subcategories) > 0) {
foreach($subcategories as $subcategory) {
$subcategory->subcategories = $CT->getCategory(sprintf($cond_cat, $level+1));
}
}
return $subcategories;
}
$i = 1;
$categories = getSubCategories($i);
?>
This will get you something like:
// $categories // first level
// foreach($categories as $cat) {
// $cat->subcategories // second level categories
// foreach($cat->subcategories as $subcategory) {
// $subcategory->subcategories // third level
// }
// }
// and so on
Please take into account that I assume that your $CT->getCategory function returns an array of category records.
If that is not the case (taking into consideration that you also add an ID="" in your query), you should also alter that function, or use a function that returns multiple category records.
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.
Here's what I've got so far-
$awards_sql_1 = mysql_query('SELECT * FROM categories WHERE section_id = 1') or die(mysql_error());
$awards_sql_2 = mysql_query('SELECT * FROM categories WHERE section_id = 2') or die(mysql_error());
$awards_sql_3 = mysql_query('SELECT * FROM categories WHERE section_id = 3') or die(mysql_error());
$awards_sql_4 = mysql_query('SELECT * FROM categories WHERE section_id = 4') or die(mysql_error());
$loop = 1;
while($row_sections = mysql_fetch_array($sections_query)) {
$category = 1;
echo "<h3>" . $row_sections['section_name'] . " (Loop# $loop)</h3>";
while($categories = mysql_fetch_array(${"awards_sql_{$loop}"})) {
${"winners_sql_{$loop}"} = mysql_query("SELECT * FROM 2009_RKR_bestof WHERE section = $loop && category = $category ORDER BY result_level ASC") or die(mysql_error());
echo "<h4><strong>{$categories['category_name']}</strong></h4>";
echo "<ul class=\"winners\">";
>> while($winners = mysql_fetch_array(${"winners_sql_{$loop}"})) {
switch ($winners['result_level']) {
case 1: $result_level = "Platinum"; break;
case 2: $result_level = "Gold"; break;
case 3: $result_level = "Silver"; break;
}
if (isset($winners['url'])) { $anchor = ""; $close = ""; }
echo "<li>$anchor{$winners['winner']}$close ($result_level)</li>";
unset($anchor);
unset($close);
}
echo "</ul>";
$category++;
}
$loop++;
}
Where I'm getting stumped, is I'm getting this thing to loop through correctly, my loop counter ($loop) is working, but when it gets time to spit out the actual reward recipients after the first loop through winners, it's only producing the category titles, the list-items are not getting looped out.
I added a little pointer to where I think the problem begins or centers around (>>).
My guess is I need to maybe unset a var somewhere, but I don't know, I can't see it.
I'm with KM - you're displaying a single page and with your loops, you've got a LOT of queries happening at once - what if 1,000 people hit that page at the same time? ouch...
Maybe consider a larger query (with some repeated data) and loop through it once?
For example:
SELECT
section_name,
category_name,
result_level,
url,
winner
FROM 2009_RKR_bestof
INNER JOIN categories ON 2009_RKR_bestof.category = categories.id
INNER JOIN sections ON 2009_RKR_bestof.section = sections.id
ORDER BY section_name,category_name ASC
In your loop, you can do checks to determine if you're in a new section (category/whatever):
//pseudo-code
$current_section = "";
while($stuff = mysql_fetch_array($sql))
{
if ($current_section == "")
{
$current_section = $stuff["section_name"];
}
if ($current_section == $stuff["section_name"])
{
//keep going in your loop
}
else
{
//we've gotten to a new section - so close your html and start a new section
}
}
You get the idea..
My guess would be that it is a data problem. It isn't having trouble reading the titles, only the winners. If it iterated once, I would check the data, and ensure that winners_sql_2 - winnders_sql_4 are getting actual data. Perhaps add an echo winners_sql_2 line, to output the contents of the query, and ensure the query is framed correctly.
I've read a lot of people discussing nested lists, but I was wondering how to iterate through an adjacancy list/tree in PHP.
I have a table with: id, title, parent_id
And I've selected all records out into an array called $pages.
Then using this php:
function makeList($pages, $used) {
if (count($pages)) {
echo "<ul>";
foreach ($pages as $page) {
echo "<li>".$page['pag_title'];
$par_id = $page['pag_id'];
$subsql("SELECT * FROM pages WHERE pag_parent = ".$par_id."");
// running the new sql through an abstraction layer
$childpages = $dbch->fetchAll();
makeList($childpages, $used, $lastused);
echo "</li>";
}
echo "</ul>";
}
}
This sort of works but I end up with any sub menu being repeated e.g.
HomeNewsSub-newsArticlesArticleNewsSub-newsArticlesArticleSub-newsArticle
I've tried adding the current id into an array that gets passed through the function, and then using in_array to check if it's there, but I have had no joy doing that.
Any help would be much appreciated.
I need to parse the whole tree so choosing parent as 0 isn't an option
Since it already does the SQL, you dont have to do it outside before the first function call.
function makeList($par_id = 0) {
//your sql code here
$subsql("SELECT * FROM pages WHERE pag_parent = $par_id");
$pages = $dbch->fetchAll();
if (count($pages)) {
echo '<ul>';
foreach ($pages as $page) {
echo '<li>', $page['pag_title'];
makeList($page['pag_id']);
echo '</li>';
}
echo '</ul>';
}
}
For storing it more tree like you might want to look at this site: Storing Hierarchical Data in a Database.
If you create an array of pages grouped by parent id it is quite easy to recursively build the list. This will only require one database query.
<?php
//example data
$items = array(
array('id'=>1, 'title'=>'Home', 'parent_id'=>0),
array('id'=>2, 'title'=>'News', 'parent_id'=>1),
array('id'=>3, 'title'=>'Sub News', 'parent_id'=>2),
array('id'=>4, 'title'=>'Articles', 'parent_id'=>0),
array('id'=>5, 'title'=>'Article', 'parent_id'=>4),
array('id'=>6, 'title'=>'Article2', 'parent_id'=>4)
);
//create new list grouped by parent id
$itemsByParent = array();
foreach ($items as $item) {
if (!isset($itemsByParent[$item['parent_id']])) {
$itemsByParent[$item['parent_id']] = array();
}
$itemsByParent[$item['parent_id']][] = $item;
}
//print list recursively
function printList($items, $parentId = 0) {
echo '<ul>';
foreach ($items[$parentId] as $item) {
echo '<li>';
echo $item['title'];
$curId = $item['id'];
//if there are children
if (!empty($items[$curId])) {
makeList($items, $curId);
}
echo '</li>';
}
echo '</ul>';
}
printList($itemsByParent);
Where is $page coming from? You might have an sql injection vulnerability in your code if you're not escaping it or using a prepared statement.
Also the SELECT statement inside a for loop jumps out as a bad practice. If the table is not that big, then select the contents of the entire table and then iterate through the result set in PHP to build the tree data structure. This could take up to n*(n-1)/2 iterations in the pathological case of your tree being a linked list. Stop when all nodes have been added to the tree, or the number of remaining nodes remains the same from one iteration to the next - this means the remaining nodes are not children of your root node.
Alternatively, if your database supports recursive SQL queries, you can use that, and it will only select the nodes that are children of your parent node. You will still have to build the tree object yourself in PHP. The form of the query would be something like:
WITH temptable(id, title, parent_id) AS (
SELECT id, title, parent_id FROM pages WHERE id = ?
UNION ALL
SELECT a.id, a.title, a.parent_id FROM pages a, temptable t
WHERE t.parent_id = a.id
) SELECT * FROM temptable
Substitute the '?' on the second line with the starting page ID.
The simplest fix would just be, when you are doing the initial select to set $pages (which you don't show), add a WHERE clause like:
WHERE pag_parent = 0
(or IS NULL, depending how you're storing "top level" pages).
That way you won't select all the children initially.
When that table gets large, recursion can get unwieldy. I wrote an blog post about a recursion-less method: http://www.alandelevie.com/2008/07/12/recursion-less-storage-of-hierarchical-data-in-a-relational-database/
Finding top parent, all parents, and all children of a node (enhancements for Tom Haigh's answer):
<?php
//sample data (can be pulled from mysql)
$items = array(
array('id'=>1, 'title'=>'Home', 'parent_id'=>0),
array('id'=>2, 'title'=>'News', 'parent_id'=>1),
array('id'=>3, 'title'=>'Sub News', 'parent_id'=>2),
array('id'=>4, 'title'=>'Articles', 'parent_id'=>0),
array('id'=>5, 'title'=>'Article', 'parent_id'=>4),
array('id'=>6, 'title'=>'Article2', 'parent_id'=>4)
);
//create new list grouped by parent id
$itemsByParent = array();
foreach ($items as $item) {
if (!isset($itemsByParent[$item['parent_id']])) {
$itemsByParent[$item['parent_id']] = array();
}
$itemsByParent[$item['parent_id']][] = $item;
}
//print list recursively
function printList($items, $parentId = 0) {
echo '<ul>';
foreach ($items[$parentId] as $item) {
echo '<li>';
echo $item['title'];
$curId = $item['id'];
//if there are children
if (!empty($items[$curId])) {
printList($items, $curId);
}
echo '</li>';
}
echo '</ul>';
}
printList($itemsByParent);
/***************Extra Functionality 1****************/
function findTopParent($id,$ibp){
foreach($ibp as $parentID=>$children){
foreach($children as $child){
if($child['id']==$id){
if($child['parent_id']!=0){
//echo $child['parent_id'];
return findTopParent($child['parent_id'],$ibp);
}else{ return $child['title'];}
}
}
}
}
$itemID=7;
$TopParent= findTopParent($itemID,$itemsByParent);
/***************Extra Functionality 2****************/
function getAllParents($id,$ibp){ //full path
foreach($ibp as $parentID=>$nodes){
foreach($nodes as $node){
if($node['id']==$id){
if($node['parent_id']!=0){
$a=getAllParents($node['parent_id'],$ibp);
array_push($a,$node['parent_id']);
return $a;
}else{
return array();
}
}
}
}
}
$FullPath= getAllParents(3,$itemsByParent);
print_r($FullPath);
/*
Array
(
[0] => 1
[1] => 2
)
*/
/***************Extra Functionality 3****************/
//this function gets all offspring(subnodes); children, grand children, etc...
function getAllDescendancy($id,$ibp){
if(array_key_exists($id,$ibp)){
$kids=array();
foreach($ibp[$id] as $child){
array_push($kids,$child['id']);
if(array_key_exists($child['id'],$ibp))
$kids=array_merge($kids,getAllDescendancy($child['id'],$ibp));
}
return $kids;
}else{
return array();//supplied $id has no kids
}
}
print_r(getAllDescendancy(1,$itemsByParent));
/*
Array
(
[0] => 2
[1] => 3
)
*/
print_r(getAllDescendancy(4,$itemsByParent));
/*
Array
(
[0] => 5
[1] => 6
)
*/
print_r(getAllDescendancy(0,$itemsByParent));
/*
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
)
*/
?>