views:

94

answers:

2

I am currently designing a forum as a personal project. One of the recurring issues I've come across is database queries in loops. I've managed to avoid doing that so far by using table joins or caching of data in arrays for later use.

Right now though I've come across a situation where I'm not sure how I can write the code in such a way that I can use either of those methods easily. However I'd still prefer to do at most 2 queries for this operation rather than 1 + 1 per group of forums, which so far has resulted in 5 per page. So while 5 isn't a huge number (though it will increase for each forum group I add) it's the principle that's important to me here, I do NOT want to write queries in loops

What I'm doing is displaying forum index groupings (eg admin forums, user forums etc) and then each forum within that group on a single page index, it's the combination of both in one page that's causing me issue. If it had just been a single group per page, I'd use a table join and problem solved. But if I use a table join here, although I can potentially get all the data I need it'll be in one mass of results and it needs displaying properly.

Here's the code (I've removed some of the html for clarity)

<?php
    $sql= "select * from forum_groups"; //query 1
    $result1 = $database->query($sql);
    while($group = mysql_fetch_assoc($result1)) //first loop
      {?>
        <table class="threads"> 
        <tr>
              <td class="forumgroupheader"> <?php echo $group['group_name']; ?> </td>
            </tr>
            <tr> 
          <td class="forumgroupheader2"> <?php echo $group['group_desc']; ?> </td>
            </tr>
       </table>
       <table>
        <tr>
        <th class="thforum"> Forum Name</th>
        <th class="thforum"> Forum Decsription</th>
        <th class="thforum"> Last Post </th>
        <tr>
            <?php 

                $group_id = $group['id'];
                $sql = "SELECT forums.id, forums.forum_group_id, forums.forum_name, forums.forum_desc, forums.visible_rank, forums.locked, forums.lock_rank, forums.topics, forums.posts, forums.last_post, forums.last_post_id, users.username
FROM forums 
LEFT JOIN users on forums.last_post_id=users.id 
WHERE forum_group_id='{$group_id}'";
                //query 2
                $result2 = $database->query($sql);
                while($forum = mysql_fetch_assoc($result2))
                                            //second loop        
                    {?>

So how can I either
a) write the SQL in such a way as to remove the second query from inside the loop or
b) combine the results in an array
either way I need to be able to access the data as an when so I can format it properly for the page output, ie within the loops still.

+2  A: 

I don't think your approach is so bad and I wouldn't change it. However, as an exercise you could do it all in one query if you select the forums (join with group) and order by group id. That way you've only made one select and you can begin looping. In your loop you would check if group_id has changed. If it has, then begin a new group header.

If this is an ultra high traffic site, you can also look into caching so pages are written to disk after they've been built.

Here's an example of how you could detect group change:

$currentGroupId = 0;
$firstTime = true;
while ($row = mysql_query_fetch_assoc($res)) {
    if ($row['group_id'] != $currentGroupId) {
        $currentGroupId = $row['group_id'];
        if (!$firstTime) {
            // echo close group html
        } else {
            $firstTime = false;
        }
        // echo open group html
    }

    // do forum stuff
}
// echo close group html
webbiedave
Thanks for that example, I've settled on 1 query with all the data in so I need to consider how to do display the results and detect how to find if the group id has changed. I will have a look at the code in a sec.
TooManyCooks
Thanks yet again for the ideas, had to modify this to take into account when there is no forum and altered a few bits, but again the example helps me understand where I need to get to! As for queries in loops, I guess it didn't matter when we're only dealing with a few queries difference, but I dislike queries being inside loops as it's a potential cause for problems later, which can be solved by better planning now. Plus it keeps me awake! +1 for both of you!
TooManyCooks
Glad I could help.
webbiedave
+3  A: 

You can do this with two queries. The first query does not need changing (although personally I'd prefer not to use SELECT *). The second query should be changed to join with forum_groups and run outside the loop, before you start iterating:

SELECT forum_groups.id, ...other columns here...
FROM forum_groups 
JOIN forums ON forum_groups.id = forum_group_id
LEFT JOIN users ON forums.last_post_id = users.id

You can also combine those two queries into one huge query that fetches all data in one go, but I'd probably not do this as it will require returning redundant data.

Mark Byers
Yeah I considered 2 queries, but I'm not sure how I then call the data. I loop through the result set for the result for the first forum group but I'm struggling with how to then measure when that forum group has changed to a new one, as then I need to close the html table and start a new one.
TooManyCooks
My post answers the question about detecting group change.
webbiedave
@Chris: Preprocess the second query in memory so that the results are organized in an easy to access form. For example: `array(1 => array(row1, row2), 2 => array(row3, row4, row5))`. Then when you iterate over the first query you can lookup the rest of the data directly. It's a bit more programming effort than what you are currently doing, but as you said, it can be advantageous to have fewer queries. As webbiedave points out, it's also possible to do it without preprocessing the data by requesting your result set sorted in the order you plan to use it, though it's perhaps slightly trickier.
Mark Byers
@Chris: It is trickier, however I want reiterate that I don't think there's really anything wrong with your original approach (nested loops).
webbiedave
@ Mark: Yes, I'd used arrays for a similar issue where I needed live user data in thread view but then I used a join with the users table instead. I considered an array here in the form you've illustrated but it's extra processing which I'm not sure needs doing though it might beat bogging the DB down. However I've taken what you said and wondered if a single query might be easier. I'll post the sql in a sec.
TooManyCooks
This is the SQL `SELECT forum_groups.id, forum_groups.group_name, forum_groups.group_desc, forum_groups.visible_rank, forum_groups.locked, forum_groups.lock_rank, forums.forum_name, forums.forum_desc, forums.visible_rank, forums.topics, forums.posts, forums.last_post, forums.last_post_idFROM forum_groups left join forums on forum_groups.id=forums.forum_group_id` it doesn't take into account the second join for the username yet, but it does return the results, without redundant sets, but there is a lot of NULL values in forums groups with no forums, but that shouldn't be the norm.
TooManyCooks