2013-09-28 4 views
3

У меня есть таблица mysql, в которой каждая запись может иметь неограниченные пользовательские поля (модель EAV, не имеет значения), и каждое поле может иметь неограниченные параметры, и каждый параметр может иметь неограниченные значения.
Сейчас я пытаюсь создать инструмент экспорта, который будет экспортировать все эти настраиваемые поля со своими значениями, то есть: name => пары значений для каждого поля. Это не важная часть, здесь просто подчеркнуть, что мы говорим о множестве запросов mysql для одной записи и что размер экспорта будет довольно большим.Экспорт большого CSV-файла

Для каждой строки из моей основной таблицы я должен сделать около 100 отдельных запросов sql для получения полей, полей и значений полей. Эти запросы довольно быстрые, потому что все они используют правильные индексы, но все же мы говорим о 100 запросах для одной записи, и я ожидаю иметь около 50 тыс. Записей в моей основной таблице только для начала.

Прямо сейчас, что я делаю это:

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, part-2.csv), а в конце, когда все записи обрабатываются, создайте архив из папки, содержащей все части csv, и заставьте браузер загрузить ее, а затем удалите ее с сервера (window.location.href изнутри последний вызов ajax).
Это хорошая альтернатива вышесказанному?

Обратите внимание: моя цель - ограничить объем использования памяти, поэтому я думаю, что второй подход поможет мне больше.

Пожалуйста, дайте мне знать, что вы думаете.
Спасибо.

+0

Если ваши данные структурированы, почему бы вам не выбрать формат JSON вместо CSV? Просто «первая мысль» ... – Powerslave

+0

Он будет использоваться в OpenOffice/Office и импортирован в другие системы, поддерживающие только csv, и json не является вариантом для этого, он должен быть csv :) – Twisted1919

ответ

4

Мой последний подход является вторым, после многих тестов я пришел к выводу, что в моем случае второй подход лучше с точки зрения использования памяти, даже если время завершения всего экспорта больше, так как GUI будет обновлять статистику в реальном времени об экспорте, и в целом это хороший пользовательский интерфейс, ожидая завершения экспорта.

Это шаги, которые я принял:
1) Загрузите страницу и сделайте первый запрос ajax на сервер.
2) Сервер будет считывать первые 1000 записей в партиях по 100 записей за раз, чтобы избежать получения сразу нескольких результатов из mysql.
3) Результаты записываются в файл как part-x.csv, где x - номер запроса, отправленный ajax.
4) Если в файл больше нет записей, последний вызов ajax создаст архив и удалит папку, содержащую файлы part-x.csv. Затем сервер вернет json param, называемый «скачать», который будет содержать URL-адрес для загрузки файла через PHP (fopen + fread + flush + 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 
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; 
?> 
Смежные вопросы