2013-09-28 146 views
3

我有一個mysql表,其中每個記錄可以有無限的自定義字段(EAV模型,這並不重要),每個字段可以有無限的選項,每個選項可以有無限的價值。
現在我正在嘗試構建一個導出工具,將導出所有這些自定義字段的值,即:每個字段的name =>值對。這不是重要的部分,它只是爲了強調我們正在討論大量針對單個記錄的mysql查詢,並且導出的大小會非常大。導出大的CSV文件

對於每一行從我的主表,我必須做的大約100單獨的SQL查詢來獲得的油田,它們的選項和字段選項的值。這些查詢速度相當快,因爲​​它們都使用正確的索引,但我們仍然在談論對單個記錄的100個查詢,並且我希望在我的主表中有大約5萬條記錄只是爲了開始。

現在,我要做的就是:

set_time_limit(0); 
ini_set('memory_limit', '1G'); 
ini_set("auto_detect_line_endings", true); 

$count = $export->count(); 
$date = date('Y-m-d-H-i-s'); 
$fileName = CHtml::encode($export->name) .'-'. $date . '.csv'; 

$processAtOnce = 100; 
$rounds = round($count/$processAtOnce); 

header("Content-disposition: attachment; filename={$fileName}"); 
header("Content-Type: text/csv"); 

$headerSet = false; 
for ($i = 0; $i < $rounds; ++$i) { 

    $limit = $processAtOnce; 
    $offset = $i * $processAtOnce; 
    $rows = $export->find($limit, $offset); 

    if (empty($rows)) { 
     continue; 
    } 

    $outStream = fopen('php://output', 'w'); 

    if (!$headerSet) { 
     fputcsv($outStream, array_keys($rows[0]), ',', '"');  
     $headerSet = true; 
    } 

    foreach ($rows as $row) { 
     fputcsv($outStream, array_values($row), ',', '"'); 
    } 

    echo fgets($outStream); 

    fclose($outStream); 
} 

基本上我指望所有的記錄和我「分頁」他們用於出口,然後通過網頁運行一次,以avoin裝載了太多的SQL結果。
我想知道這是否是一種有效的方法?有什麼想法嗎?

我的替代方法是計算所有的記錄,將它們分成「頁」,併爲每個頁面做一個Ajax請求(先前的請求已經被成功地進行了後稱爲遞歸函數)。在執行ajax請求時,可能會一次處理1k條記錄(這些1k也會像上例中一樣拆分,例如在內部運行10次(例如,使用100個結果),將它們寫入臨時目錄(如part-1.csv,部分2.csv),並在最後處理所有記錄時,從包含所有csv部分的文件夾中創建一個存檔,並強制瀏覽器下載它,然後將其從服務器中刪除(window.location.href最後ajax調用)。
這是一個很好的替代上述?

請注意,我的目標是要限制使用內存,這就是爲什麼我認爲第二個方法將有助於我更多的量。

請讓我知道您的想法。
謝謝。

+0

如果您的數據結構,你爲什麼不選擇JSON格式而不是CSV?只是一個「第一個想法」... – Powerslave

+0

它將用於OpenOffice/Office,並在其他只支持csv的系統中導入,而json不是這個選項,它必須是csv :) – Twisted1919

回答

4

我的最後一個方法是第二個,經過大量的測試,我的結論是,在我的情況下,第二種方法是在內存使用方面的方式更好,即使要完成整個出口的時間越長,沒有按」這很重要,因爲GUI將更新有關導出的實時統計信息,並且在等待導出完成時總體而言具有良好的用戶體驗。

這些是我採取的步驟:
1)加載頁面並向服務器發出第一個ajax請求。
2)服務器將一次讀取100條記錄中的前1000條記錄,以避免從mysql中立即獲得多條結果。
3)結果以part-x.csv的形式寫入文件,其中x是由ajax發送的請求編號。
4)當沒有更多記錄添加到文件中時,最後的ajax調用將創建歸檔,並刪除包含part-x.csv文件的文件夾。然後,服務器將返回名爲「下載」一個JSON PARAM其中將包含網址通過PHP下載文件(FOPEN + FREAD +沖洗+ FCLOSE,然後斷開鏈接的歸檔文件)
5)使用「下載」參數,瀏覽器將執行window.location.href = json.download並強制文件被下載。

我知道,這是更多這樣的工作,但正如我所說,最終結果似乎比只是一次加載我所做的第一次。

0

感謝帖子Twisted1919給了我一些啓發。我知道這篇文章有點老了,但我想我會發布我的解決方案的一些代碼,以防萬一它幫助其他人。

它使用一些Wordpress函數進行數據庫查詢。

我正在用您的步驟3和4替換。

<?php 
// if its a fist run truncate the file. else append the file 
if($start==0) { 
    $handle = fopen('temp/prod-export'. '.csv', 'w'); 
}else{ 
    $handle = fopen('temp/prod-export'. '.csv', 'a'); 
} 
?> 

一些基本的jQuery

<script> 
    // do stuff on the form submit 
    $('#export-form').submit(function(e){ 
     e.preventDefault(); 
     var formData = jQuery('#export-form').serializeObject(); 
     var chunkAndLimit = 1000; 
     doChunkedExport(0,chunkAndLimit,formData,$(this).attr('action'),chunkAndLimit); 
    }); 
    // function to trigger the ajax bit 
    function doChunkedExport(start,limit,formData,action,chunkSize){ 
     formData['start'] = start; 
     formData['limit'] = limit; 
     jQuery.ajax({ 
      type : "post", 
      dataType : "json", 
      url : action, 
      data : formData, 
      success: function(response) { 
       console.log(response); 
       if(response.result=='next'){ 
        start = start + chunkSize; 
        doChunkedExport(start,limit,formData,action,chunkSize); 
       }else{ 
        console.log('DOWNLOAD'); 
       } 
      } 
     }); 
    } 
    // A function to turn all form data into a jquery object 
    jQuery.fn.serializeObject = function(){ 
     var o = {}; 
     var a = this.serializeArray(); 
     jQuery.each(a, function() { 
      if (o[this.name] !== undefined) { 
       if (!o[this.name].push) { 
        o[this.name] = [o[this.name]]; 
       } 
       o[this.name].push(this.value || ''); 
      } else { 
       o[this.name] = this.value || ''; 
      } 
     }); 
     return o; 
    }; 
</script> 

的PHP位

<?php 
global $wpdb; 

$postCols = array(
    'post_title', 
    'post_content', 
    'post_excerpt', 
    'post_name', 
); 

header("Content-type: text/csv"); 

$start = intval($_POST['start']); 
$limit = intval($_POST['limit']); 

// check the total results to workout the finish point 
$query = "SELECT count(ID) as total FROM `wp_posts` WHERE post_status = 'publish';"; 
$results = $wpdb->get_row($query, ARRAY_A); 
$totalResults = $results['total']; 
$result = 'next'; 
if(($start + $limit) >= $totalResults){ 
    $result = 'finished'; 
} 

// if its a fist run truncate the file. else append the file 
if($start==0) { 
    $handle = fopen('temp/prod-export'. '.csv', 'w'); 
}else{ 
    $handle = fopen('temp/prod-export'. '.csv', 'a'); 
} 

$cols = implode(',',$postCols); 
//The query 
$query = "SELECT {$cols} FROM `wp_posts` WHERE post_status = 'publish' LIMIT {$start},{$limit};"; 
$results = $wpdb->get_results($query, ARRAY_A); 

if($start==0) { 
    $headerDisplayed = false; 
}else{ 
    $headerDisplayed = true; 
} 

foreach ($results as $data) { 
    // Add a header row if it hasn't been added yet 
    if (!$headerDisplayed) { 
     // Use the keys from $data as the titles 
     fputcsv($handle, array_keys($data)); 
     $headerDisplayed = true; 
    } 
    // Put the data into the stream 
    fputcsv($handle, $data); 
} 

// Close the file 
fclose($handle); 

// Output some stuff for jquery to use 
$response = array(
    'result'  => $result, 
    'start'   => $start, 
    'limit'   => $limit, 
    'totalResults' => $totalResults 
); 
echo json_encode($response); 


// Make sure nothing else is sent, our file is done 
exit; 
?>