4

When I execute a PDO statement, internally a result set is stored, and I can use ->fetch() to get a row from the result.

If I wanted to convert the entire result to an array, I could do ->fetchAll().

With Laravel, in the Query Builder docs, I only see a way to get an array result from executing the query.

// example query, ~30,000 rows

$scores = DB::table("highscores")
            ->select("player_id", "score")
            ->orderBy("score", "desc")
            ->get();

var_dump($scores);
// array of 30,000 items...
// unbelievable ...

Is there any way to get a result set from Query Builder like PDO would return? Or am I forced to wait for Query Builder to build an entire array before it returns a value ?

Perhaps some sort of ->lazyGet(), or ->getCursor() ?


If this is the case, I can't help but see Query Builder is an extremely short-sighted tool. Imagine a query that selects 30,000 rows. With PDO I can step through row by row, one ->fetch() at a time, and handle the data with very little additional memory consumption.

Laravel Query Builder on the other hand? "Memory management, huh? It's fine, just load 30,000 rows into one big array!"


PS yes, I know I can use ->skip() and ->take() to offset and limit the result set. In most cases, this would work fine, as presenting a user with 30,000 rows is not even usable. If I want to generate large reports, I can see PHP running out of memory easily.

3 Answers 3

4

After @deczo pointed out an undocumented function ->chunk(), I dug around in the source code a bit. What I found is that ->chunk() is a convenience wrapper around multiplying my query into several queries queries but automatically populating the ->step($m)->take($n) parameters. If I wanted to build my own iterator, using ->chunk with my data set, I'd end up with 30,000 queries on my DB instead of 1.

This doesn't really help, too, because ->chunk() takes a callback which forces me to couple my looping logic at the time I'm building the query. Even if the function was defined somewhere else, the query is going to happen in the controller, which should have little interest in the intricacies of my View or Presenter.

Digging a little further, I found that all Query Builder queries inevitably pass through \Illuminate\Database\Connection#run.

// https://github.com/laravel/framework/blob/3d1b38557afe0d09326d0b5a9ff6b5705bc67d29/src/Illuminate/Database/Connection.php#L262-L284

/**
 * Run a select statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return array
 */
public function select($query, $bindings = array())
{
  return $this->run($query, $bindings, function($me, $query, $bindings)
  {
    if ($me->pretending()) return array();

    // For select statements, we'll simply execute the query and return an array
    // of the database result set. Each element in the array will be a single
    // row from the database table, and will either be an array or objects.
    $statement = $me->getReadPdo()->prepare($query);

    $statement->execute($me->prepareBindings($bindings));

    return $statement->fetchAll($me->getFetchMode());
  });
}

See that nasty $statement->fetchAll near the bottom ?

That means arrays for everyone, always and forever; your wishes and dreams abstracted away into an unusable tool Laravel Query Builder.

I can't express the valley of my depression right now.


One thing I will say though is that the Laravel source code was at least organized and formatted nicely. Now let's get some good code in there!

Sign up to request clarification or add additional context in comments.

1 Comment

Use PDO instead. Query Builder and Eloquent are just another layer to make things easier to develop. It's not one to rule them all thing that does everything you wish and dream, it always has its price. And, yes, the code is pretty good, though still inconsistent and buggy in many places.
3

Use chunk:

DB::table('highscores')
   ->select(...)
   ->orderBy(...)
   ->chunk($rowsNumber, function ($portion) {
      foreach ($portion as $row) { // do whatever you like }
   });

Obviously returned result will be just the same as calling get, so:

$portion; // array of stdObjects

// and for Eloquent models:
Model::chunk(100, function ($portion) {
    $portion; // Collection of Models
});

3 Comments

I see no documentation for this. Can you help me learn more about it?
Check on Eloquent Chunking Results laravel.com/docs/eloquent#basic-usage or in the source laravel.com/api/4.2/Illuminate/Database/Query/… . I suggest checking the Illuminate\Database\Query\Builder class directly, comments there are pretty much everything you need.
Thanks for drawing this to my attention. The existence of undocumented APIs (on their website) made me dig into the source code to see what's really going on. I posted an answer of my own.
3

Here is a way to use the laravel query builder for making the query, but to then use the underlying pdo fetch to loop over the record set which I believe will solve your problem - running one query and looping the record set so you don't run out of memory on 30k records.

This approach will use all the config stuff you setup in laravel so you don't have to config pdo separately.

You could also abstract out a method to make this easy to use that takes in the query builder object, and returns the record set (executed pdo statement), which you would then while loop over as below.

$qb = DB::table("highscores")
        ->select("player_id", "score")
        ->orderBy("score", "desc");

$connection = $qb->getConnection();

$pdo = $connection->getPdo();

$query = $qb->toSql();
$bindings = $qb->getBindings();

$statement = $pdo->prepare($query);
$statement->execute($bindings);

while ($row = $statement->fetch($connection->getFetchMode()))
{
    // do stuff with $row
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.