{"id":200,"date":"2023-09-26T15:27:19","date_gmt":"2023-09-26T14:27:19","guid":{"rendered":"https:\/\/mrzebra.co.uk\/code\/?p=200"},"modified":"2023-09-26T15:32:32","modified_gmt":"2023-09-26T14:32:32","slug":"how-to-perform-an-unbuffered-query-in-laravel","status":"publish","type":"post","link":"https:\/\/zebra-north.com\/code\/2023\/09\/26\/how-to-perform-an-unbuffered-query-in-laravel\/","title":{"rendered":"How to Perform an Unbuffered Query in Laravel"},"content":{"rendered":"\n<p>If you wish to process a very large number of rows from a database then it may be prohibitively expensive to load them all into memory first.  Laravel&#8217;s <code>chunk()<\/code> function may look like the solution however this performs a query for each chunk and is therefore extremely slow.<\/p>\n\n\n\n<p>The ideal solution is to disable query buffering and thereby only ever have a single row in memory at once.  Note that this option is specific to the MySQL PDO driver.<\/p>\n\n\n\n<!--more-->\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-php\">\/\/ Disable query buffering on the connection.\n$connection = DB::connection();\n$pdo = $connection-&gt;getReadPdo();\n$pdo-&gt;setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);\n\n\/\/ Build your query.\n$query = DB::table(&#039;users&#039;)-&gt;select(&#039;*&#039;)-&gt;orderBy(&#039;name&#039;);\n\n\/\/ Execute the query and iterate over each row.\nforeach ($connection-&gt;cursor($query-&gt;toSql(), $query-&gt;getBindings()) as $row) {\n    var_dump($row);\n}\n\n\/\/ Re-enable query buffering.\n$pdo-&gt;setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);<\/code><\/pre>\n\n\n\n<p>This code first fetches the PDO connection to the database and disables query buffering.  You can use Laravel&#8217;s query builder as normal to construct your query.  The connection&#8217;s <code>cursor()<\/code> function is used to iterate over every row.<\/p>\n\n\n\n<p>Note that you must re-enable query buffering after you have finished, or later queries will be affected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Unbuffered HTTP Output<\/h2>\n\n\n\n<p>If you wish to return the data from an HTTP endpoint, for example as a data feed, then you must also make sure that the output is not buffered.  This is accomplished by using the <code>stream()<\/code> or <code>streamDownload()<\/code> method on the response.<\/p>\n\n\n\n<p><code>streamDownload()<\/code> takes four parameters:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A callback that produces the page output via echo\/print\/etc.<\/li>\n\n\n\n<li>The filename, which defaults to the route name if <code>null<\/code>.<\/li>\n\n\n\n<li>Additional HTTP headers, used here to set the <code>Content-Type<\/code>.<\/li>\n\n\n\n<li>The HTTP disposition, which defaults to <code>attachment<\/code>.<\/li>\n<\/ol>\n\n\n\n<p>The example below shows how to add CSV output to your controller class.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-php\">use Illuminate\\Http\\Request;\nuse Symfony\\Component\\HttpFoundation\\StreamedResponse;\n\nclass MyApiController extends Controller\n{\n    public function csv(Request $request): StreamedResponse\n    {\n        return response()-&gt;streamDownload([$this, &#039;outputCsv&#039;], null, [&#039;Content-Type&#039; =&gt; &#039;text\/csv&#039;]);\n    }\n\n    private function outputCsv(): void\n    {\n        \/\/ Disable query buffering on the connection.\n        $connection = DB::connection();\n        $pdo = $connection-&gt;getReadPdo();\n        $pdo-&gt;setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);\n\n        \/\/ Build your query.\n        $query = DB::table(&#039;users&#039;)-&gt;select(&#039;name, email&#039;)-&gt;orderBy(&#039;name&#039;);\n\n        \/\/ Execute the query and iterate over each row.\n        foreach ($connection-&gt;cursor($query-&gt;toSql(), $query-&gt;getBindings()) as $row) {\n            echo $row-&gt;name . &#039;,&#039; . $row-&gt;email. &quot;\\n&quot;;\n        }\n\n        \/\/ Re-enable query buffering.\n        $pdo-&gt;setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);    }\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>If you wish to process a very large number of rows from a database then it may be prohibitively expensive to load them all into memory first. Laravel&#8217;s chunk() function may look like the solution however this performs a query for each chunk and is therefore extremely slow. The ideal solution is to disable query [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[11],"class_list":["post-200","post","type-post","status-publish","format-standard","hentry","category-php","tag-laravel"],"_links":{"self":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/200","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/comments?post=200"}],"version-history":[{"count":3,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/200\/revisions"}],"predecessor-version":[{"id":203,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/200\/revisions\/203"}],"wp:attachment":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/media?parent=200"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/categories?post=200"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/tags?post=200"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}