-4

Function render makes website 500% slow! Can anyone fix that please ? Someone told me :

because it sends a database request on each iteration of the loop (it's not the only problem with this chunk of code but it's the most taxing one)

Yes I understand what that means. His way is:

you need to get all of the data before you start building the menu, then you just insert the data instead of requesting more data on each iteration

But i don't know how i must do it!

<?php
$menu_html='';
function render_menu($parent_id,$actmenuid)
{
    $obj = new Database(); 
    $con = $obj->dbconnectt();
    global $menu_html;
    $result=mysqli_query($con, "select * from tbl_menu where parent_id='$parent_id'");
    
    if(mysqli_num_rows($result)==0) return;

    if($parent_id==0){
        $menu_html.='<ul class="topnav">';
    }else{
        $menu_html.='<ul>';
    }
    while($row=mysqli_fetch_array($result)) {
        $childnum = $obj->recordcount("SELECT * FROM tbl_menu WHERE parent_id='".$row['id']."'");
        if($childnum == 0){ 
            $linkvalue='/category/'.$row['id'].'.html';
        } else{ 
            $linkvalue='#';
        }
        
        if($row['id']==$actmenuid && $actmenuid !=NULL){
            $actv='class="active"';
        }else{
            $actv='';
        }
        
        $menu_html.='<li '.$actv.'><a href="'.$linkvalue.'">'.$row['title'].'</a>';
        render_menu($row['id'],$actmenuid);
        $menu_html.='</li>';
    }
    $menu_html.='</ul>';return $menu_html;
}
if($isDsh==false){
    echo render_menu(0,$actmenuid);
}
?>
Funk Forty Niner
  • 74,450
  • 15
  • 68
  • 141

4 Answers4

0

Depending on how many records you have, try removing this query from inside the loop since it's running for every record on the first query.

$childnum = $obj->recordcount("SELECT * FROM tbl_menu WHERE parent_id='".$row['id']."'");

Change it a single query like this where it returns counts for each parent idea, and place it outside of the loop:

$parentcount = mysqli_query($con, ("SELECT parent_id, count(*) FROM tbl_menu GROUP BY parent_id");

There may be other issues, so please post the database structure and number of records that you're working with too.

Spudly
  • 310
  • 1
  • 2
  • 10
  • more than 1000 records – mahdi jn Jul 05 '20 at 01:23
  • @mahdijn It's hard to read this db structure. In the future, update the question to show stuff like this in code format. Like others have mentioned, 1,000 records is not a lot, so try moving your second query outside the loop (use any of the answers posted here). All three answers are addressing the problem of having a query inside your while loop, so any of these approaches should fix the problem. – Spudly Jul 05 '20 at 02:09
0

basically you need to do what @spudly has suggested. But there is a small catch in his solution which depending on the number of the rows in yous tbl_menu table you may use a big chunk of memory to fetch all the records. you can optimise it more with using his solution but changing the query to:

select
    parent_tbl_menu.id,
    count(child_tbl_menu.id) as cnt
from
    tbl_menu as parent_tbl_menu
left join
    tbl_menu as child_tbl_menu
on parent_tbl_menu.id = child_tbl_menu.parent_id
where
    parent_tbl_menu.parent_id = ?
group by
    parent_tbl_menu.id

This way you will only fetch the child records of a specific parent. And please consider using prepared statements as your code has sql injection vulnerability.

EhsanT
  • 2,077
  • 3
  • 27
  • 31
0

Don't make recursive queries.

  1. Having "more than 1000" rows is not too big. You can simply call everything from the table into php, then perform the recursive html build in php this will have a memory overhead, but far less processing overhead because you only ever make one trip to the db.

  2. Alternatively (when your db table is prohibitively large), you should avoid gathering rows unnecessarily by adding a new column. The new column will store all "descendants" for the respective row when the row is INSERTed or update it when it is UPDATEd. Then you only need to reference this column when needing to call specific rows. In other words, do the recursive processing only once (when writing to the db) AND not when needing to display the data. This will, again, produce a finite result set in one query which can then be recursively traversed to build the desired output.

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
0
  • Connect (from PHP to MySQL) only once for the entire web page.
  • Don't put a SELECT inside a loop if you can do all the work in a single SELECT, such as with a JOIN. (Exception: A "hierarchical" table needs the nested SELECT. Exception to the exception: MySQL 8.0 and MariaDB 10.2 can do it with a "recursive CTE".)
  • Don't fetch all the columns (SELECT *) when all you want it is a recordcount. Instead, SELECT COUNT(*) ... and use the number returned.
  • 1000 of anything is probably excessive for a web page. Re-think the UI.
Rick James
  • 135,179
  • 13
  • 127
  • 222