2

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>';

The table image for the category

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

enter image description here

Now how to exclude subcategory from parent list?

Community
  • 1
  • 1
Code Lover
  • 8,099
  • 20
  • 84
  • 154
  • Oh! I never knew that I can use other query too. :P new to mysql. Can you tell me how exactly I should use? – Code Lover Nov 19 '12 at 09:38
  • check this answer http://stackoverflow.com/a/7631222/1823486, you have to use recursive function for it – gegokk Nov 19 '12 at 09:47

2 Answers2

1

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
    ...
}
mychalvlcek
  • 3,956
  • 1
  • 19
  • 34
  • But it is giving me in the order it's been created so if sub category created in last than it is displaying after all categories. Also not excluding from parent list – Code Lover Nov 19 '12 at 09:52
  • Alright! i have added `if($catid['parentid'] == null)` to the first foreach and it works. but don't know either it is correct way or not – Code Lover Nov 19 '12 at 10:03
  • yea, in 1st query you have to select only rows with parentid = 0, so after that you will firstly select top-level cats (parent cats) and then lower-level cats – mychalvlcek Nov 19 '12 at 10:29
1

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.

Vyktor
  • 20,559
  • 6
  • 64
  • 96
  • This would be great way BUT because of some reason I cannot add column. So any other way to get it without adding new column or updating the database. – Code Lover Nov 19 '12 at 09:51
  • @pixelngrain you've got it there now – Vyktor Nov 19 '12 at 10:07
  • Not actually in perfect way. It is adding only for first child and than second child and so on doesn't shows up in proper order. – Code Lover Nov 19 '12 at 10:09
  • @pixelngrain data isn't sorted... Take a look what's inside `$all_elements`. – Vyktor Nov 19 '12 at 10:14
  • Oh cool let me try this if it works.. I really appreciate your detailed help. However for your knowledge I am creating plugin for some CMS and it is depending on the user how many categories he/she would have so no idea about what amount of data wold be there. And that is one of the reason I cannot create separate column for this – Code Lover Nov 19 '12 at 10:22
  • @pixelngrain nothing it should contain list of children `id`s (so you could reference `$all_elements`. If category has children with ids: `(5,8,16)`, value of this array should be `(5,8,16)` – Vyktor Nov 19 '12 at 10:39
  • giving me this error `Warning: Illegal string offset 'children' in C:\xampp\htdocs\... : eval()'d code on line 76 Fatal error: Cannot use string offset as an array in C:\xampp\htdocs\.. : eval()'d code on line 76` – Code Lover Nov 19 '12 at 11:01
  • @pixelngrain I was aiming to give you an idea how to do this, not to implement whole thing for you. Please do your work trying to understand how does it work and fix errors like invalid offsets on your own. – Vyktor Nov 19 '12 at 11:04
  • Okay thanks a lot for your great help. Was asking as not have enough knowledge. But anyway. Appreciate your time and effort. – Code Lover Nov 19 '12 at 11:11