среда, 30 декабря 2020 г.

Порционная обработка записей в цикле

Прямо вот совсем недавно на стеке возник вопрос - как порционно загружать записи из CSV в БД? Вопрос-то простой, но дополнительным условием было - после окончания цикла while нельзя делать дополнительный запрос на вставку.

Простое и понятное решение:

$handle = fopen(/* ... */);
$batch = [];
while (($data = fgetcsv($handle, 4096, ';')) !== false) {
  $batch[] = processData($data);

  if(100 === \count($batch)) {
    runBatchInsert($batch);

    $batch = [];
  }
}
fclose($handle);
if ($batch) {
    runBatchInsert($batch);
}

Как мы видим - здесь мы собираем записи в массив $batch, и как только в этом массиве будет 100 элементов - выполняем запрос на вставку в функции runBatchInsert(). Понятно, что после завершения цикла в $batch могут находиться данные, которые также надо вставить, что мы и делаем вторым вызовом функции runBatchInsert().

Однако, по условию задачи мы не должны использовать второй вызов runBatchInsert(). Это сделать вполне возможно, однако читабельность кода немного ухудшится, поэтому придется добавить немного комментариев, чтобы через месяц не забыть какого лешего это написано именно так, а не как у всех. В результате получаем такой вот код:

$handle = fopen(/* ... */);
$batch = [];
// запускаем "вечный" цикл
while (true) {
  $data = fgetcsv($handle, 4096, ';');
  // если данные есть - добавляем их в $batch
  if (false !== $data) {
    $batch[] = processData($data);
  }

  // если размер батча достиг лимита или мы больше не получили данных
  if(100 === \count($batch) || false === $data) {
    // в батче есть данные - вставляем
    if ($batch) {
      runBatchInsert($batch);
    }

    // данных больше нет - прерываем цикл
    if (false === $data) {
      break;    
    }

    // очищаем батч
    $batch = [];
  }
}
fclose($handle);

Вы сами вольны выбирать какой из подходов использовать, но лично я использую первый.