views:

75

answers:

4

I have a table which contains a list of categories and and another table which contains a list of products in each category.

e.g.

CatID | Cat_Name
----------------
1     | Books
2     | CDs

and

ProductID | Product_Name     | CatID
------------------------------------
1         |The Bible         | 1
2         |The Koran         | 1
3         |90s Greatest Hits | 2
4         |80s Greatest Hits | 2

what I want to do is get

<ul>
<li>Books</li>
   <ul>
   <li>The Bible</li>
   <li>The Koran</li>
   </ul>
<li>Cds</li>
   <ul>
   <li>90s Greatest Hits </li>
   <li>00s Greatest Hits </li>
   </ul>
 </ul>

without doing (PHP)

$query = mysql_query("SELECT ... FROM categories")

while($row = mysql_fetch_assoc($query)):

$query2 = mysql_query("SELECT ... FROM products WHERE catId = $row['CatId'])

endwhile;

How can I move away from using nested while/SELECT statements, can I do it efficiently with one query - and how do I access the relevant data using PHP?

EDIT: I was under the impression that if your tables become quite large, multiple SQL queries will slow your page, and therefore to optimize page load you should try to reduce the number of queries?

+2  A: 

You could select all the data at once using a join...

SELECT *
    FROM category JOIN products ON catgegory.CatID = products.CatID
    ORDER BY products.CatID;

but I can't really see anything terribly wrong with your current approach.

Brian Hooper
I agree, I don't think this will be more efficient than your current approach
Prot0
Surely the less time a db connection is open the better.
Ash Burlaczenko
@Brian Hooper : Yes but then how to do you access the data such to produce the unordered list using PHP?@Ash Burlaczenko: This is what I thought?
Ashley Ward
@Ashley Ward: First idea that comes to mind is to track the category name, and `echo ($old_category ? "</ul> : ""), "<li>$category</li><ul>"` when it changes. Then echo the data. There's probably an easier way, but you have to account for the first category somehow.
cHao
@Ash Burlaczenko: The connection's going to need to stay open either way -- possibly even longer with a bunch of queries than with a single one. SQL parsing ain't free, and there's also the cost of deciding what to query for, creating the query, and passing it off to the server.
cHao
@Ashley Ward: The best bet would be to specify the columns you want, aliasing them if necessary, and then they'll be available in the $row array as you have it now. Do you want me to expand on this?
Brian Hooper
A: 
SELECT * FROM category JOIN products USING(CatID);
Centurion
+6  A: 

select all products and category names using JOIN

select * from category c, products p where c.catID = p.catID order by p.catID

fetch and group results

$products_by_cat = array();
foreach(fetch(....) as $record)
    $products_by_cat[$record->catName][] = $record;

you'll get a 2-D array like

 [books] => array(bible, koran)
 [CDs]   => array(cd1, cd2, ....)

output this array with two nested loops

 foreach($products_by_cat as $cat => $products)
    <li> $cat  <ul>
    foreach($products as $p)
        <li> $p->name </li>
    </li></ul>
stereofrog
+1  A: 

stereofrog's answer is very good. I'd like to point out that you can print your list without looping through the result set more than once. This can be useful if you have a lot of items.

To do so, make sure the query result is ordered by category. Create a variable to keep track of the 'current category' and set it to something that will not be a category id, like 'x'. Loop through the result and every time the CatID does not match your current category value, you know you've changed categories and need to print the category header. Reset the current category value to the new CatID value.

That's enough in some list situations. In your case, you'll also need to figure out when a category is ending so you can end your lists properly. You can do this by looking-ahead in the result array to see if the next category matches your current category value and acting accordingly.

With only a few categories, it really doesn't matter how you do it. If you have thousands of rows with a lot of data, this method can be a little faster and use less memory. It's a little harder to figure out logically than stereofrog's method. But it only loops through the data once, and it does not create a duplicate of the data, which can be handy if you have megabytes of stuff to loop through.

Scott Saunders
@Scott Saunders. This is a great explanation, especially the part about looking ahead in the array. Well explained. Thanks
Ashley Ward