PDO Pagination - Fatal error: Out of memory

Asked

Viewed 223 times

1

I am making a pagination, but it is generating a memory error.

Is there any way to optimize or fix the fault? Or is it on the same server?

Note: the php.ini this set the memory in 512.

if (!(isset($_GET['pagenum']))) {
    $pagenum = 1;
} else {
    $pagenum = $_GET['pagenum'];
}
$page_limit = ($_GET["show"] <> "" && is_numeric($_GET["show"]) ) ? $_GET["show"] : 30;

try {
    $keyword = trim($_GET["keyword"]);
    if (!empty($keyword)) {
        $sql = "SELECT * FROM noticias WHERE conteudo LIKE :keyword OR titulo LIKE :keyword ORDER BY Nid DESC";
        $stmt = $DB->prepare($sql);

        $likekeyword = "%".$keyword."%";
        $stmt->bindParam(':keyword', $likekeyword, PDO::PARAM_STR);

    } else {
        $sql = "SELECT * FROM noticias WHERE 1 ORDER BY Nid DESC";
        $stmt = $DB->prepare($sql);
    }

    $stmt->execute();
    $total_count = count($stmt->fetchAll());

    $last = ceil($total_count / $page_limit);

    if ($pagenum < 1) {
        $pagenum = 1;
    } elseif ($pagenum > $last) {
        $pagenum = $last;
    }

    $lower_limit = ($pagenum - 1) * $page_limit;
    $lower_limit = ($lower_limit < 0) ? 0 : $lower_limit;

    $sql2 = $sql . " limit " . ($lower_limit) . " ,  " . ($page_limit) . " ";

    $stmt = $DB->prepare($sql2);

    if ($keyword <> "" ) {
        $stmt->bindParam(':keyword', $likekeyword, PDO::PARAM_STR);
    }

    $stmt->execute();
    $results = $stmt->fetchAll();
} catch (Exception $ex) {
    echo $ex->getMessage();
}

code displays counters

 <div class="col-lg-12 center">
  <ul class="pagination pagination-sm">
   <?php
   for ($i = 1; $i <= $last; $i++) {
   if ($i == $pagenum) {
    ?>
   <li class="active"><a href="javascript:void(0);" ><?php echo $i ?></a></li>
            <?php
          } else {
            ?>
            <li><a href="noticias_listar.php?pagenum=<?php echo $i; ?>&keyword=<?php echo $_GET["keyword"]; ?>" class="links"  onclick="displayRecords('<?php echo $page_limit; ?>', '<?php echo $i; ?>');" ><?php echo $i ?></a></li>
            <?php
          }
        }
        ?>
      </ul>
    </div>
  • You need to add the limit in that select with two likes.

  • either limit and difinifo by the $page_limit variable, or it’s the way I did it wrong?

  • At your appointment, after ORDER BY Nid DESC add LIMIT 0, 70 just for testing see if the error will happen. The way the query is returned is the largest number of records, with this limit only the first 70 will be returned, of course you need to change this mechanism to work dynamically. See that answer

  • putting in the select with 2 Likes the error continued the same already putting in the other was the following -> SQLSTATE[42000]: Syntax error or access Violation: 1064 You have an error in your SQL syntax; check the manual that Corresponds to your Mysql server version for the right syntax to use near 'limit 0 , 30' at line 1

  • Put there the query that was wrong.

  • And another you are running the same query twice ... one without limit and the other with.

  • rray $sql = "SELECT * FROM noticias WHERE 1 ORDER BY Nid DESC LIMIT 0,30"; in case I am running 2 pq if the user does not type anything it lists everything if type in the search field it searches with the other

  • I slapped the question :)

  • The way the system is is more likely to receive errors than the expected result, an obvious example is this - $total_count = count($stmt->fetchAll()); - that can cause overload, and still maintains the values, and then another query is executed, with the method fetchAll(). There are own functions to count the number of lines found, and there are also clauses own to count the number of lines, examples are the function COUNT of SQL and the fetchColumns or simply rowCount to the PDO.

  • $total_count = count($stmt->fetchAll()); more here is not using exactly Count? there is difference between rowCount($stmt->fetchAll()) and Count($stmt->fetchAll())?

  • 1

    There is, and much, being that the count iterates each array position, object properties, and method rowCount is basically an additive to consultation, that is, it already exists at the time a query is executed, and besides, it is not used with the fetchAll, is used directly in the consultation $stmt->rowCount, as the function columnCount. Another option is a native SQL function - COUNT.

Show 6 more comments

3 answers

2

It’s a bad idea to use PDOStatement::fetchAll() if you have no idea the amount of records that will be returned and stored in memory. Keep in mind that class PDOStatement works also as an iterator, avoiding the premeditated use of memory. In your case, use PDOStatement::rowCount() to have in hand the number of records and itere with the method PDOStatement::fetch().

I particularly prefer to run a query with COUNT(*) before, to get the total number of records, and another definitive query to retrieve them, using mechanisms with OFFSET and LIMIT. It makes no sense to request lines from the database that will not be used during execution.

1

Try separating some of the variables from the functions, so you can better visualize what’s going on.

As I said earlier in the comments, there are several alternatives to the function count you were using along with the method fetchAll, and that were being repeated unnecessary times. Recalling also that the fetchAll has as default search method the PDO::FETCH_BOTH.

<?php

$dsn = "mysql:host=localhost;dbname=exemplo;charset=utf8;";
$pagina = isset($_GET['pagina']) ? (int)$_GET['pagina'] : 1;
$pagina = ($pagina > 0) ? $pagina : 1;
$termo_busca = isset($_GET['s']) ? (string)$_GET['s'] : '';
$por_pagina = 3;
$offset = ($pagina -1) * $por_pagina;
$dados = array();
$total = "";
$i = 1;

try {
    $pdo = new pdo($dsn, 'root', '');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $sql = "SELECT * FROM noticias WHERE conteudo LIKE :busca OR titulo LIKE :busca ORDER BY Nid DESC LIMIT {$por_pagina} OFFSET {$offset}";
    if($prepare = $pdo->prepare($sql)){
        if($prepare->execute(array(':busca'=>'%'.$termo_busca.'%'))){
            while($linha = $prepare->fetch(PDO::FETCH_OBJ)){
                $dados[] = $linha;
            }
            # condição para corrigir a paginacao quando existe um termo de busca
            if($termo_busca):
                $where = "WHERE conteudo LIKE '%{$termo_busca}%' OR titulo LIKE '%{$termo_busca}%'";
            else:
                $where = "";
            endif;  

            $count = $pdo->query("SELECT COUNT(*) FROM notcias {$where}")->fetchColumn();
            $total = ceil($count/$por_pagina);
        }
    }
} catch(PDOException $e){
    die($e->getMessage());
}

# imprimir os resultados e paginar
if($dados){
    foreach($dados as $object){
        print $object->titulo . "<br/>";
    }

    do{
        if($termo_busca):
            print "<a href=\"{$_SERVER['PHP_SELF']}?s={$termo_busca}&pagina={$i}\"> {$i} </a>";
        else:
            print "<a href=\"{$_SERVER['PHP_SELF']}?pagina={$i}\"> {$i} </a>";
        endif;

        $i++;
    } while($i <= $total);
} else {
    print "Nenhum resltado encontrado <a href=\"{$_SERVER['PHP_SELF']}\">voltar</a>";
}

?>

This query is internal, and returns the number of results corresponding to the given criterion:

$pdo->query("SELECT COUNT(*) FROM notcias {$where}"); # retorna COUNT(*)=> #numero

The function fetchColumn returns a single column in the following row of the result set. Adding to the query Teriror, it returns the exact result, and the function COUNT(*) returns only one column for as a result of the query.

$pdo->query("SELECT COUNT(*) FROM notcias {$where}")->fetchColumn();

Or you can still use the query this way:

$pdo->query("SELECT * FROM exemplo {$where}")->rowCount(); 

There are also other ways to count the number of existing lines in the database without overloading the memory, you just have to search.

1

By error, the one with Where query seems to be the problem, the idella is to page it, another problem is that this query is executed twice. Behold:

} else {
    $sql = "SELECT * FROM noticias WHERE 1 ORDER BY Nid DESC";
    $stmt = $DB->prepare($sql);
}
$stmt->execute(); //primeira vez que a consulta é executada.
$total_count = count($stmt->fetchAll());

//... código omitido.

$sql2 = $sql . " limit " . ($lower_limit) . " ,  " . ($page_limit) . " ";
$stmt = $DB->prepare($sql2);

//código omitido.

$stmt->execute(); //segunda vez que a mesma consulta é executada.
$results = $stmt->fetchAll();

I believe the code can be simplified in this way:

$pagenum = isset($_GET['pagenum']) ? $_GET['pagenum'] : 1;

$limite = ($_GET["show"] <> "" && is_numeric($_GET["show"]) ) ? $_GET["show"] : 30;
$offset = ($pagenum * $limit) - $limit; 


try {
    $keyword = trim($_GET["keyword"]);
    if (!empty($keyword)) {
        $sql = "SELECT * FROM noticias WHERE conteudo LIKE :keyword OR titulo LIKE :keyword ORDER BY Nid DESC";
        $stmt = $DB->prepare($sql);

        $likekeyword = "%".$keyword."%";
        $stmt->bindParam(':keyword', $likekeyword, PDO::PARAM_STR);

    } else {
        $sql = "SELECT * FROM noticias WHERE 1 ORDER BY Nid DESC LIMIT :limit, :offset";
        $stmt = $DB->prepare($sql);
        $stmt->bindValue(':limit', $limite, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    }

    $stmt->execute();
    $results = $stmt->fetchAll();

    //Faz o calculo do número de registros por página
    $total_registros = $DB->query("SELECT COUNT(*) FROM noticias")->fetchColumn() or die($db->error);
    $paginas = floor($total_registros / $limite);



} catch (Exception $ex) {
    echo $ex->getMessage();
}


//imprime os resultados e os link das páginas(segundo for).
foreach($results as $item){
    printf("id: %d - título: %s - idade: %d <br>", $item['id'], $item['titulo']);
}


for($i = 1; $i <= $paginas; $i++){
    printf('<a href="?page=%d">%d</a>|', $i, $i);
}

Reference:

PHP script paging

  • Well I didn’t get a chance to test, I contacted the hosting staff and they said they unlocked something in apache about the bug. Even so I tested the changes you posted in the answer and ok, it worked almost perfect except for the page counters that was only on the first page ( updated the question with the code of where displays the page counters)

  • Your page link needs to have two information, the limit for example 30 and the off set that is the 'current position' of the forward record that should be displayed. @Arsomnolasco

Browser other questions tagged

You are not signed in. Login or sign up in order to post.