2011-01-12 20 views
24

Ho grandi fogli di lavoro Excel che voglio essere in grado di leggere in MySQL usando PHPExcel.Come leggere fogli di lavoro di grandi dimensioni da file Excel di grandi dimensioni (27 MB +) con PHPExcel?

Sto usando il recent patch che consente di leggere nei fogli di lavoro senza aprire l'intero file. In questo modo posso leggere un foglio di lavoro alla volta.

Tuttavia, un file di Excel ha una larghezza di 27 MB. Posso leggere con successo nel primo foglio di lavoro poiché è piccolo, ma il secondo foglio di lavoro è così grande che il processo cron che ha avviato il processo alle 22:00 non è stato completato alle 8:00, il foglio di lavoro è troppo grande.

C'è un modo per leggere in un foglio di lavoro riga per riga, ad es. qualcosa di simile:

$inputFileType = 'Excel2007'; 
$inputFileName = 'big_file.xlsx'; 
$objReader = PHPExcel_IOFactory::createReader($inputFileType); 
$worksheetNames = $objReader->listWorksheetNames($inputFileName); 

foreach ($worksheetNames as $sheetName) { 
    //BELOW IS "WISH CODE": 
    foreach($row = 1; $row <=$max_rows; $row+= 100) { 
     $dataset = $objReader->getWorksheetWithRows($row, $row+100); 
     save_dataset_to_database($dataset); 
    } 
} 

Addendum

@ Marco, ho usato il codice che avete inviato per creare il seguente esempio:

function readRowsFromWorksheet() { 

    $file_name = htmlentities($_POST['file_name']); 
    $file_type = htmlentities($_POST['file_type']); 

    echo 'Read rows from worksheet:<br />'; 
    debug_log('----------start'); 
    $objReader = PHPExcel_IOFactory::createReader($file_type); 
    $chunkSize = 20; 
    $chunkFilter = new ChunkReadFilter(); 
    $objReader->setReadFilter($chunkFilter); 

    for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { 
     $chunkFilter->setRows($startRow, $chunkSize); 
     $objPHPExcel = $objReader->load('data/' . $file_name); 
     debug_log('reading chunk starting at row '.$startRow); 
     $sheetData = $objPHPExcel->getActiveSheet()->toArray(null, true, true, true); 
     var_dump($sheetData); 
     echo '<hr />'; 
    } 
    debug_log('end'); 
} 

Come mostra il file di log, si corre bene su un piccolo file 8K Excel, ma quando lo eseguo su un file Excel 3 MB, non viene mai superato l'abete st pezzo, c'è un modo per ottimizzare il codice per le prestazioni, in caso contrario non sembra non è abbastanza performante per ottenere pezzi di un file di grandi dimensioni di Excel:

2011-01-12 11:07:15: ----------start 
2011-01-12 11:07:15: reading chunk starting at row 2 
2011-01-12 11:07:15: reading chunk starting at row 22 
2011-01-12 11:07:15: reading chunk starting at row 42 
2011-01-12 11:07:15: reading chunk starting at row 62 
2011-01-12 11:07:15: reading chunk starting at row 82 
2011-01-12 11:07:15: reading chunk starting at row 102 
2011-01-12 11:07:15: reading chunk starting at row 122 
2011-01-12 11:07:15: reading chunk starting at row 142 
2011-01-12 11:07:15: reading chunk starting at row 162 
2011-01-12 11:07:15: reading chunk starting at row 182 
2011-01-12 11:07:15: reading chunk starting at row 202 
2011-01-12 11:07:15: reading chunk starting at row 222 
2011-01-12 11:07:15: end 
2011-01-12 11:07:52: ----------start 
2011-01-12 11:08:01: reading chunk starting at row 2 
(...at 11:18, CPU usage at 93% still running...) 

Addendum 2

Quando io commento:

//$sheetData = $objPHPExcel->getActiveSheet()->toArray(null, true, true, true); 
//var_dump($sheetData); 

Poi si analizza ad una velocità accettabile (circa 2 righe al secondo), è comunque necessario aumentare le prestazioni di toArray()?

2011-01-12 11:40:51: ----------start 
2011-01-12 11:40:59: reading chunk starting at row 2 
2011-01-12 11:41:07: reading chunk starting at row 22 
2011-01-12 11:41:14: reading chunk starting at row 42 
2011-01-12 11:41:22: reading chunk starting at row 62 
2011-01-12 11:41:29: reading chunk starting at row 82 
2011-01-12 11:41:37: reading chunk starting at row 102 
2011-01-12 11:41:45: reading chunk starting at row 122 
2011-01-12 11:41:52: reading chunk starting at row 142 
2011-01-12 11:42:00: reading chunk starting at row 162 
2011-01-12 11:42:07: reading chunk starting at row 182 
2011-01-12 11:42:15: reading chunk starting at row 202 
2011-01-12 11:42:22: reading chunk starting at row 222 
2011-01-12 11:42:22: end 

Addendum 3

Questo sembra funzionare in modo adeguato, per esempio, almeno sul file 3 MB:

for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { 
    echo 'Loading WorkSheet using configurable filter for headings row 1 and for rows ', $startRow, ' to ', ($startRow + $chunkSize - 1), '<br />'; 
    $chunkFilter->setRows($startRow, $chunkSize); 
    $objPHPExcel = $objReader->load('data/' . $file_name); 
    debug_log('reading chunk starting at row ' . $startRow); 
    foreach ($objPHPExcel->getActiveSheet()->getRowIterator() as $row) { 
     $cellIterator = $row->getCellIterator(); 
     $cellIterator->setIterateOnlyExistingCells(false); 
     echo '<tr>'; 
     foreach ($cellIterator as $cell) { 
      if (!is_null($cell)) { 
       //$value = $cell->getCalculatedValue(); 
       $rawValue = $cell->getValue(); 
       debug_log($rawValue); 
      } 
     } 
    } 
} 
+0

La var_dump di $ sheetData era solo nella mia frammento di codice per dimostrare come funziona il chunking, probabilmente non qualcosa che ci serve in un utilizzo "mondo reale". Il metodo rangeToArray() che sto attualmente aggiungendo alla classe del foglio di lavoro sarebbe anche più efficiente del metodo toArray() se fosse necessario eseguire un dump dei dati del foglio di lavoro. –

+0

@Edward Tanguay ciao, hai trovato qualche soluzione/alternativa per questo? Sto avendo lo stesso problema –

+1

Un'alternativa a PHPExcel è la libreria open source [Spout] (https://github.com/box/spout). Supporta la lettura e la scrittura di file enormi e non richiede più di 10 MB di memoria. Ed è super veloce! – Adrien

risposta

9

E 'possibile leggere un foglio di lavoro "pezzi" usando i filtri di lettura, anche se non posso garantire l'efficienza.

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example2.xls'; 


/** Define a Read Filter class implementing PHPExcel_Reader_IReadFilter */ 
class chunkReadFilter implements PHPExcel_Reader_IReadFilter 
{ 
    private $_startRow = 0; 

    private $_endRow = 0; 

    /** Set the list of rows that we want to read */ 
    public function setRows($startRow, $chunkSize) { 
     $this->_startRow = $startRow; 
     $this->_endRow  = $startRow + $chunkSize; 
    } 

    public function readCell($column, $row, $worksheetName = '') { 
     // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow 
     if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) { 
      return true; 
     } 
     return false; 
    } 
} 


echo 'Loading file ',pathinfo($inputFileName,PATHINFO_BASENAME),' using IOFactory with a defined reader type of ',$inputFileType,'<br />'; 
/** Create a new Reader of the type defined in $inputFileType **/ 

$objReader = PHPExcel_IOFactory::createReader($inputFileType); 



echo '<hr />'; 


/** Define how many rows we want to read for each "chunk" **/ 
$chunkSize = 20; 
/** Create a new Instance of our Read Filter **/ 
$chunkFilter = new chunkReadFilter(); 

/** Tell the Reader that we want to use the Read Filter that we've Instantiated **/ 
$objReader->setReadFilter($chunkFilter); 

/** Loop to read our worksheet in "chunk size" blocks **/ 
/** $startRow is set to 2 initially because we always read the headings in row #1 **/ 

for ($startRow = 2; $startRow <= 240; $startRow += $chunkSize) { 
    echo 'Loading WorkSheet using configurable filter for headings row 1 and for rows ',$startRow,' to ',($startRow+$chunkSize-1),'<br />'; 
    /** Tell the Read Filter, the limits on which rows we want to read this iteration **/ 
    $chunkFilter->setRows($startRow,$chunkSize); 
    /** Load only the rows that match our filter from $inputFileName to a PHPExcel Object **/ 
    $objPHPExcel = $objReader->load($inputFileName); 

    // Do some processing here 

    $sheetData = $objPHPExcel->getActiveSheet()->toArray(null,true,true,true); 
    var_dump($sheetData); 
    echo '<br /><br />'; 
} 

noti che questo filtro Leggi leggerà sempre la prima riga del foglio di lavoro, nonché le righe definiti dalla regola pezzo.

Quando si utilizza un filtro di lettura, PHPExcel analizza ancora l'intero file, ma solo carica quelle cellule che corrispondono al filtro leggere definito, in modo che utilizza solo la memoria richiesta da quel numero di cellule.Tuttavia, analizzerà il file più volte, una volta per ogni blocco, quindi sarà più lento. Questo esempio legge 20 righe alla volta: semplicemente riga per riga, imposta semplicemente $ chunkSize su 1.

Questo può anche causare problemi se hai formule che fanno riferimento a celle in "blocchi" diversi, perché i dati semplicemente non sono t disponibile per celle al di fuori dell'attuale "chunk".

+0

Sto provando il tuo codice in un file di test, ma mi dice "Class" ChunkReadFilter "non trovato". Se tolgo il comando 'implements PHPExcel_Reader_IReadFilter', trova la classe e mi dice che devo' implementare l'interfaccia PHPExcel_Reader_IReadFilter', ho messo all'inizio del mio file 'require_once 'PHPExcelClasses/PHPExcel/Reader/IReadFilter.php'' e 'require_once 'PHPExcelClasses/PHPExcel/Reader/IReader.php'' ma non riesco ancora a trovare la classe se implemento questa interfaccia, c'è qualche altro file che devo includere? –

+0

ho appena dovuto mettere la classe 'ChunkReadFilter' sopra il codice principale, funziona ora, grazie –

+0

Ho provato quel codice in un test (pubblicato sopra). Sebbene funzioni su un file piccolo (8K), sembra che non passi il primo blocco su un file di 3 MB. –

3

Attualmente leggere .xlsx, .csv e .ods la migliore opzione è foglio di calcolo-reader (https://github.com/nuovo/spreadsheet-reader), perché in grado di leggere i file senza caricare tutto in memoria. Per l'estensione .xls presenta limitazioni poiché utilizza PHPExcel per la lettura.

1

/* * Questa è la ChunkReadFilter.php */

<?php 
Class ChunkReadFilter implements PHPExcel_Reader_IReadFilter { 

    private $_startRow = 0; 
    private $_endRow = 0; 

    /** Set the list of rows that we want to read */ 
    public function setRows($startRow, $chunkSize) { 
     $this->_startRow = $startRow; 
     $this->_endRow = $startRow + $chunkSize; 
    } 

    public function readCell($column, $row, $worksheetName = '') { 

     // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow 
     if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) { 

      return true; 
     } 
     return false; 
    } 

} 
?> 

/* * E questa è l'index.php e un non perfetto, ma di base implementazione alla fine * di questo file. */

<?php 

require_once './Classes/PHPExcel/IOFactory.php'; 
require_once 'ChunkReadFilter.php'; 

class Excelreader { 

    /** 
    * This function is used to read data from excel file in chunks and insert into database 
    * @param string $filePath 
    * @param integer $chunkSize 
    */ 
    public function readFileAndDumpInDB($filePath, $chunkSize) { 
     echo("Loading file " . $filePath . " ....." . PHP_EOL); 
     /** Create a new Reader of the type that has been identified * */ 
     $objReader = PHPExcel_IOFactory::createReader(PHPExcel_IOFactory::identify($filePath)); 

     $spreadsheetInfo = $objReader->listWorksheetInfo($filePath); 

     /** Create a new Instance of our Read Filter * */ 
     $chunkFilter = new ChunkReadFilter(); 

     /** Tell the Reader that we want to use the Read Filter that we've Instantiated * */ 
     $objReader->setReadFilter($chunkFilter); 
     $objReader->setReadDataOnly(true); 
     //$objReader->setLoadSheetsOnly("Sheet1"); 
     //get header column name 
     $chunkFilter->setRows(0, 1); 
     echo("Reading file " . $filePath . PHP_EOL . "<br>"); 
     $totalRows = $spreadsheetInfo[0]['totalRows']; 
     echo("Total rows in file " . $totalRows . " " . PHP_EOL . "<br>"); 

     /** Loop to read our worksheet in "chunk size" blocks * */ 
     /** $startRow is set to 1 initially because we always read the headings in row #1 * */ 
     for ($startRow = 1; $startRow <= $totalRows; $startRow += $chunkSize) { 
      echo("Loading WorkSheet for rows " . $startRow . " to " . ($startRow + $chunkSize - 1) . PHP_EOL . "<br>"); 
      $i = 0; 
      /** Tell the Read Filter, the limits on which rows we want to read this iteration * */ 
      $chunkFilter->setRows($startRow, $chunkSize); 
      /** Load only the rows that match our filter from $inputFileName to a PHPExcel Object * */ 
      $objPHPExcel = $objReader->load($filePath); 
      $sheetData = $objPHPExcel->getActiveSheet()->toArray(null, true, true, false); 

      $startIndex = ($startRow == 1) ? $startRow : $startRow - 1; 
      //dumping in database 
      if (!empty($sheetData) && $startRow < $totalRows) { 
       /** 
       * $this->dumpInDb(array_slice($sheetData, $startIndex, $chunkSize)); 
       */ 

       echo "<table border='1'>"; 
       foreach ($sheetData as $key => $value) { 
        $i++; 
        if ($value[0] != null) { 
         echo "<tr><td>id:$i</td><td>{$value[0]} </td><td>{$value[1]} </td><td>{$value[2]} </td><td>{$value[3]} </td></tr>"; 
        } 
       } 
       echo "</table><br/><br/>"; 
      } 
      $objPHPExcel->disconnectWorksheets(); 
      unset($objPHPExcel, $sheetData); 
     } 
     echo("File " . $filePath . " has been uploaded successfully in database" . PHP_EOL . "<br>"); 
    } 

    /** 
    * Insert data into database table 
    * @param Array $sheetData 
    * @return boolean 
    * @throws Exception 
    * THE METHOD FOR THE DATABASE IS NOT WORKING, JUST THE PUBLIC METHOD.. 
    */ 
    protected function dumpInDb($sheetData) { 

     $con = DbAdapter::getDBConnection(); 
     $query = "INSERT INTO employe(name,address)VALUES"; 

     for ($i = 1; $i < count($sheetData); $i++) { 
      $query .= "(" . "'" . mysql_escape_string($sheetData[$i][0]) . "'," 
        . "'" . mysql_escape_string($sheetData[$i][1]) . "')"; 
     } 

     $query = trim($query, ","); 
     $query .="ON DUPLICATE KEY UPDATE name=VALUES(name), 
       =VALUES(address), 
       "; 
     if (mysqli_query($con, $query)) { 
      mysql_close($con); 
      return true; 
     } else { 
      mysql_close($con); 
      throw new Exception(mysqli_error($con)); 
     } 
    } 

    /** 
    * This function returns list of files corresponding to given directory path 
    * @param String $dataFolderPath 
    * @return Array list of file 
    */ 
    protected function getFileList($dataFolderPath) { 
     if (!is_dir($dataFolderPath)) { 
      throw new Exception("Directory " . $dataFolderPath . " is not exist"); 
     } 
     $root = scandir($dataFolderPath); 
     $fileList = array(); 
     foreach ($root as $value) { 
      if ($value === '.' || $value === '..') { 
       continue; 
      } 
      if (is_file("$dataFolderPath/$value")) { 
       $fileList[] = "$dataFolderPath/$value"; 
       continue; 
      } 
     } 
     return $fileList; 
    } 

} 

$inputFileName = './prueba_para_batch.xls'; 
$excelReader = new Excelreader(); 
$excelReader->readFileAndDumpInDB($inputFileName, 500); 
Problemi correlati