Processing a large result set with php - php

My scenario is like this ,i have a huge dataset fetched from mysql table
$data = $somearray; //say the number of records in this array is 200000
iam looping this data,processing some functionalities and writing this data to an excel file
$my_file = 'somefile.csv';
$handle = fopen($my_file, 'w') or die('Cannot open file: ' . $my_file); file
for($i=0;$i<count($data);$i++){
//do something with the data
self::someOtherFunctionalities($data[$i]); //just some function
fwrite($handle, $data[$i]['index']); //here iam writing this data to a file
}
fclose($handle);
My problem is that the loop gets memory exhaustion ...it shows "fatal error allowed memory size of.." is there anyway to process this loop without exhaustion
Due to the server limitation im unable to increase php memory limit like
ini_set("memory_limit","2048M");
Im not concerned about the time it takes..even if it takes hours..so i did set_time_limit(0)

your job is linear and you don't need load all data. use Unbuffered Query also use php://stdout(don't temp file) if send this file to httpClient.
<?php
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
$uresult = $mysqli->query("SELECT Name FROM City", MYSQLI_USE_RESULT);
$my_file = 'somefile.csv'; // php://stdout
$handle = fopen($my_file, 'w') or die('Cannot open file: ' . $my_file); file
if ($uresult) {
while ($row = $uresult->fetch_assoc()) {
// $row=$data[i]
self::someOtherFunctionalities($row); //just some function
fwrite($handle, $row['index']); //here iam writing this data to a file
}
}
$uresult->close();
?>

Can you use "LIMIT" in your MySQL query?
The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants (except when using prepared statements).
With two arguments, the first argument specifies the offset of the first row to return, and the second specifies the maximum number of rows to return. The offset of the initial row is 0 (not 1):
SELECT * FROM tbl LIMIT 5,10; # Retrieve rows 6-15
http://dev.mysql.com/doc/refman/5.0/en/select.html

If you don't worry about time, take 1000 rows at a time, and just append the rows to the end of the file, eg. make a temp file that you move and/or rename when the job is done.
First select count(*) from table
then for($i = 0; i < number of row; i = i + 1000){
result = SELECT * FROM table LIMIT i,1000; # Retrieve rows 6-15
append to file = result
}
move and rename the file
this is verry meta code, but the process should work

Related

Advice on returning results from a large query returning error- Allowed memory size of 734003200 bytes exhausted

I'm trying to return the result of 3 tables being joined together for a user to download as CSV, and this is throwing the error:
Allowed memory size of 734003200 bytes exhausted
This is the query being run:
SELECT *
FROM `tblProgram`
JOIN `tblPlots` ON `tblPlots`.`programID`=`tblProgram`.`pkProgramID`
JOIN `tblTrees` ON `tblTrees`.`treePlotID`=`tblPlots`.`id`
The line of code causing the error is this:
$resultsALL=$this->db->query($fullQry);
Where $fullQry is the query shown above. When I comment out that single line, everything runs without the error. So I'm certain its not an infinite loop somewhere I'm missing.
I'm wondering how do I break up the query so that I can get the results without erroring out? The tables only have a relatively small amount of data in them right now and will be even larger eventually, so I don't think raising the memory size is a good option.
I'm using CodeIgniter/php/mysql. I can provide more code if need be...
Thank you for any direction you can advise!
Based off of: MySQL : retrieve a large select by chunks
You may also try retrieving the data in chunks by using the LIMIT clause.
Since you're using CodeIgniter 3, here is how you can go about it.
You may need to pass a different $orderBy argument#6 to the getChunk(...) method if in case your joined tables have conflicting id column names.
I.e: $this->getChunk(..., ..., ..., 0, 2000, "tblProgram.id");
Solution:
<?php
class Csv_model extends CI_Model
{
public function __construct()
{
parent::__construct();
$this->load->database();
}
public function index()
{
$sql = <<< END
SELECT *
FROM `tblProgram`
JOIN `tblPlots` ON `tblPlots`.`programID`=`tblProgram`.`pkProgramID`
JOIN `tblTrees` ON `tblTrees`.`treePlotID`=`tblPlots`.`id`
END;
$this->getChunk(function (array $chunk) {
/*
* Do something with each chunk here;
* Do something with each chunk here;
* log_message('error', json_encode($chunk));
* */
}, $this->db, $sql);
}
/*
* Processes a raw SQL query result in chunks sending each chunk to the provided callback function.
* */
function getChunk(callable $callback, $DBContext, string $rawSQL = "SELECT 1", int $initialRowOffset = 0, int $maxRows = 2000, string $orderBy = "id")
{
$DBContext->query('DROP TEMPORARY TABLE IF EXISTS chunkable');
$DBContext->query("CREATE TEMPORARY TABLE chunkable AS ( $rawSQL ORDER BY `$orderBy` )");
do {
$constrainedSQL = sprintf("SELECT * FROM chunkable ORDER BY `$orderBy` LIMIT %d, %d", $initialRowOffset, $maxRows);
$queryBuilder = $DBContext->query($constrainedSQL);
$callback($queryBuilder->result_array());
$initialRowOffset = $initialRowOffset + $maxRows;
} while ($queryBuilder->num_rows() === $maxRows);
}
}
Use getUnbufferedRow() for processing large result sets.
getUnbufferedRow()
This method returns a single result row without prefetching the whole
result in memory as row() does. If your query has more than one row,
it returns the current row and moves the internal data pointer ahead.
$query = $db->query("YOUR QUERY");
while ($row = $query->getUnbufferedRow()) {
echo $row->title;
echo $row->name;
echo $row->body;
}
For use with MySQLi you may set MySQLi’s result mode to
MYSQLI_USE_RESULT for maximum memory savings. Use of this is not
generally recommended but it can be beneficial in some circumstances
such as writing large queries to csv. If you change the result mode be
aware of the tradeoffs associated with it.
$db->resultMode = MYSQLI_USE_RESULT; // for unbuffered results
$query = $db->query("YOUR QUERY");
$file = new \CodeIgniter\Files\File(WRITEPATH.'data.csv');
$csv = $file->openFile('w');
while ($row = $query->getUnbufferedRow('array'))
{
$csv->fputcsv($row);
}
$db->resultMode = MYSQLI_STORE_RESULT; // return to default mode
Note:
When using MYSQLI_USE_RESULT all subsequent calls on the same
connection will result in error until all records have been fetched or
a freeResult() call has been made. The getNumRows() method will
only return the number of rows based on the current position of the
data pointer. MyISAM tables will remain locked until all the records
have been fetched or a freeResult() call has been made.
You can optionally pass ‘object’ (default) or ‘array’ in order to
specify the returned value’s type:
$query->getUnbufferedRow(); // object
$query->getUnbufferedRow('object'); // object
$query->getUnbufferedRow('array'); // associative array
freeResult()
It frees the memory associated with the result and deletes the result
resource ID. Normally PHP frees its memory automatically at the end of
script execution. However, if you are running a lot of queries in a
particular script you might want to free the result after each query
result has been generated in order to cut down on memory consumption.
$query = $thisdb->query('SELECT title FROM my_table');
foreach ($query->getResult() as $row) {
echo $row->title;
}
$query->freeResult(); // The $query result object will no longer be available
$query2 = $db->query('SELECT name FROM some_table');
$row = $query2->getRow();
echo $row->name;
$query2->freeResult(); // The $query2 result object will no longer be available

Querying from database and writing the result to text file

I have a query selects all from the database table and writes it to a text file. If the state is small (say max of 200k rows), the code still works and writes it to the text file. Problem arises when I have a state that has 2M rows when queried, then there's also the fact that the table has 64 columns.
Here's a part of the code:
create and open file
$file = "file2.txt";
$fOpen = fopen($file, "a"); // Open file, write and append
$qry = "SELECT * FROM tbl_two WHERE STE='48'";
$res = mysqli_query($con, $qry);
if(!$res) {
echo "No data record" . "<br/>";
exit;
}
$num_res =mysqli_num_rows($res);
for ($i=0; $i<=$num_res; $i++) {
$row = mysqli_fetch_assoc ($res);
$STATE = (trim($row['STATE'] === "") ? " " : $row['STATE']);
$CTY = (trim($row['CTY']=== "") ? " " : $row['CTY']);
$ST = (trim($row['ST']=== "") ? " " : $row['ST']);
$BLK = (trim($row['BLK']=== "") ? " " : $row['BLK']);
....
....
//64th column
$data = "$STATE$CTY$ST$BLK(to the 64th variable)\r\n";
fwrite($f,$data);
}
fclose($f);
I tried putting a limit to the query:
$qry = "SELECT * FROM tbl_two WHERE STE='48' LIMIT 200000";
Problem is, it just writes until the 200kth line, and it doesn't write the remaining 1.8m lines.
If I don't put a limit to the query, it encounters the error Out of memory .... . TIA for any kind suggestions.
First you need to use buffer query for fetching the data Read it
Queries are using the buffered mode by default. This means that query results are immediately transferred from the MySQL Server to PHP and then are kept in the memory of the PHP process.
Unbuffered MySQL queries execute the query and then return a resource while the data is still waiting on the MySQL server for being fetched. This uses less memory on the PHP-side, but can increase the load on the server. Unless the full result set was fetched from the server no further queries can be sent over the same connection. Unbuffered queries can also be referred to as "use result".
NOTE: buffered queries should be used in cases where you expect only a limited result set or need to know the amount of returned rows before reading all rows. Unbuffered mode should be used when you expect larger results.
Also optimize the array try to put variable directly and you while loop only
pdo = new PDO("mysql:host=localhost;dbname=world", 'my_user', 'my_pass');
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$uresult = $pdo->query("SELECT * FROM tbl_two WHERE STE='48' LIMIT 200000");
if ($uresult) {
$lineno = 0;
while ($row = $uresult->fetch(PDO::FETCH_ASSOC)) {
echo $row['Name'] . PHP_EOL;
// write value in text file
$lineno++;
}
}

php odbc_exec not returning all results

We are currently running some complex queries which return large data sets in MS SQL, exporting to Excel, and providing these as reports to our users. What we are trying to do is automate these reports on Drupal (running on Windows Server) by executing the query and writing the results to a file for the users to download.
Here is what one query looks like, it returns 86,972 rows, contains 66 columns, and executes in 134 seconds (when executed in MS SQL 2008 client):
Select with 16 JOINS
UNION
Select with 7 JOINS
UNION
Select with 12 JOINS
UNION
Select with 11 JOINS
When I execute this query via PHP, only 3608 rows are returned and the execution time has been anywhere from 70 seconds to 300 seconds. I have also checked the ini_get('max_execution_time') which is 1200, so it is not timing out.
When I simplify this query by only executing the first Select statement group (which only returns 47,158 rows, not 86,972) and reducing the number of columns, I do get more results:
Columns - Rows returned
66 - 3608
58 - 4059
32 - 8105
16 - 32,051
11 - 32,407
5 - 47,158
I am at a loss for what to do, it seems as though executing the query via PHP does not return the same results as when I run this in the MS SQL client. Does anyone know are there any limits on the data set which is returned when using ODBC_EXEC or ODBC_EXECUTE (I tried that too)? Any help is much appreciated!
Here is the PHP code that we are using:
$dbhandle = odbc_connect($odbcmgt_dsn,$odbcmgt_user,$odbcmgt_pwd,SQL_CUR_USE_ODBC);
$queryResult = odbc_exec($dbhandle, $sql);
$filename = 'path\test.csv';
$delimiter = ',';
//print max execution time
$maxEx = ini_get('max_execution_time');
dpm("Max Execution Time: " . $maxEx);
//open file to write into
$fp = fopen($filename,'w');
//retrieve number of columns, print column names to array
$columnHeaderArray = array();
$numColsRet = odbc_num_fields($queryResult);
for($i = 1; $i <= $numColsRet; $i++){
$columnHeaderArray[$i] = odbc_field_name($queryResult,$i);
}
$numRowsRet = 0;
//print column names to the file
fputcsv($fp,$columnHeaderArray,$delimiter);
//print each row to the file
while($row = odbc_fetch_array($queryResult)) {
fputcsv($fp, $row,$delimiter);
$numRowsRet++;
}
//print connection error if any
$error = odbc_errormsg($dbhandle);
dpm("error message: ".$error);
if($error != '') { drupal_set_message('<pre>'.var_export($error,true). '</pre>'); }
//print number of rows
dpm("Num records: " . $numRowsRet);
//close file that was written into
fclose($fp);
odbc_close($dbhandle);

Write postgres output to a file using php

I want to get the output of a postgres query written into a file. I am using php to connect to the remote database and execute the query. Here is the sample code.
$connection_id=pg_connect("host=localhost dbname=test user=test password=test");
$psql="select example from sample limit 180";
$result=pg_query($connection_id,$psql);
I have the query executed, but I am unable to write it to a file. How do I do that?
Help is really appreciated.
You cannot write the query result into a file directly. The result returned by pg_query is no string with any data that can be printed or written into a file. It's either an error status (false) or some kind of "reference" to result data kept for this query.
If $result isn't ==false and if PostgreSQL could find any rows as a result for your query, then you can fetch these rows. But that's an extra step. It's not included in pg_query. In order to check how many result rows were found you can use the function pg_num_rows.
Then you can iterate through the result set using pg_fetch_assoc. This is only one suitable function. There are a few more, e.g. pg_fetch_row.
Here's some small example code (quick & dirty without much error handling):
<?php
// Set the output of this script to plain text
header("Content-Type: text/plain");
$conn = pg_connect("..."); // insert your data here
if (!$conn) die ("Couldn't connect.");
$result = pg_query($conn, "SELECT example FROM ..."); // TODO
// Check for error and check the number of rows found:
if ((!$result) || (pg_num_rows($result) < 1)) {
pg_close();
echo "Couldn't find any data for your query. Maybe the query is wrong (error) or there are no matching lines.";
exit;
}
// Line counter for screen output
$i = 1;
// Open file. (Important: Script must have write permission for the directory!)
$fileHandle = fopen("./myresults.txt", "w");
// Do this as long as you can find more result rows:
while ($row = pg_fetch_assoc($result)) {
// Write value to the output that is sent to the web browser client:
echo "Line " . $i . ": \"" . strip_tags($row['example']) . "\"\r\n";
// Write the same value as a new line into the opened file:
fwrite ($fileHandle, $row['example'] . "\r\n";
// Increase line number counter:
$i++;
}
// Close the file:
fclose ($fileHandle);
// Free the result / used memory:
pg_free_result($result);
pg_close();
?>

php+odbc writing to files limitation

i got a function in PHP to read table from ODBC (to IBM AS400) and write it to a text file on daily basis. it works fine until it reach more than 1GB++. Then it just stop to some rows and didn't write completely.
function write_data_to_txt($table_new, $query)
{
global $path_data;
global $odbc_db, $date2;
if(!($odbc_rs = odbc_exec($odbc_db,$query))) die("Error executing query $query");
$num_cols = odbc_num_fields($odbc_rs);
$path_folder = $path_data.$table_new."/";
if (!file_exists($path_folder)) mkdir ($path_folder,0777);
$filename1 = $path_folder. $table_new. "_" . $date2 . ".txt";
$comma = "|";
$newline = chr(13).chr(10);
$handle = fopen($filename1, "w+");
if (is_writable($filename1)) {
$ctr=0;
while(odbc_fetch_row($odbc_rs))
{
//function for writing all field
// for($i=1; $i<=$num_cols; $i++)
// {
// $data = odbc_result($odbc_rs, $i);
// if (!fwrite($handle, $data) || !fwrite($handle, $comma)) {
// print "Cannot write to file ($filename1)";
// exit;
// }
//}
//end of function writing all field
$data = odbc_result($odbc_rs, 1);
fwrite($handle,$ctr.$comma.$data.$newline);
$ctr++;
}
echo "Write Success. Row = $ctr <br><br>";
}
else
{
echo "Write Failed<br><br>";
}
fclose($handle);
}
no errors, just success message but it should be 3,690,498 rows (and still increase) but i just got roughly 3,670,009 rows
My query is ordinary select like :
select field1 , field2, field3 , field4, fieldetc from table1
What i try and what i assume :
I think it was fwrite limitation so i try not to write all field (just write $ctr and 1st record) but it still stuck in same row.. so i assume its not about fwrite exceed limit..
I try to reduce field i select and it can works completely!! so i assume it have some limitation on odbc.
I try to use same odbc datasource with SQL Server and try to select all field and it give me complete rows. So i assume its not odbc limitation.
Even i try on 64 bits machine but it even worse, it just return roughly 3,145,812 rows.. So i assume it's not about 32/64 bit infrastructure.
I try to increase memory_limit in php ini to 1024mb but it didnt work also..
Is there anyone know if i need to set something in my PHP to odbc connection??

Categories