Чтение большого файла CSV по 100 строк средствами сервера. Возможно ли?

Приветствую. Была задача загрузить данные из файла формата csv в БД. В файле два столбца, грубо говоря ключ и значение, и 270000 строк. Проблема была в том, что если читать в цикле построчно и сразу писать в БД сервер не вывозит, старшие товарищи в группе Телеграм сказали надо читать по 100 строк и потом записывать в базу. Я не придумал ничего лучше, чем дёргать скрипт чтения файла с фронта ajax'jv каждый раз меняя $offset. Вопрос, можно ли это же сделать без запросов с фронта, а внутри скрипта, но при этом чтобы инициализация происходила с фронта. Другими словами у меня так: уходит первый запрос, если в ответе есть $offset, уходит второй запрос с этим $offset и так пока не вернётся false. А хочется, чтобы ушёл запрос, скрипт отработал и я показал результат. Возможно ли такое в условиях ограничения времени выполнения скрипта и объемов памяти?

Мой вариант скрипта.
<?php
if ($snippet = $modx->getObject('modSnippet', array('name' => 'formit'))) {
    $properties = $snippet->getProperties();
    $property_set = !empty($set)
                ? $snippet->getPropertySet($set)
                : array();

    $scriptProperties = array_merge($properties, $property_set, $scriptProperties);
    $snippet->_cacheable = false;
    $snippet->_processed = false;

    $response = $snippet->process($scriptProperties);
    $response = json_decode($AjaxForm->handleFormIt($scriptProperties),1);
    
} else {
    return  $AjaxForm->error('Что-то пошло не так :-(', array());
}


if ($response['success'] && $_POST['filename']) {
    $pathToFile = MODX_ASSETS_PATH . $_POST['filename'];
    $data = false;
    $count = 0;
    $length = 20;
    $row = 0;
    $fields = array();
    $offset = (int)$_POST['offset'] ?: 0;
    $handle = fopen($pathToFile, "r");
    if ($handle !== FALSE) {
        if($offset*$length < filesize($pathToFile)){
            fseek($handle, $offset*$length);
            
            while ($row < 100) {
                $fields = fgetcsv($handle, 20, ";");
                $personal_account = (string)$fields[0];
                $kvc_number = $fields[1];
                if($personal_account && $kvc_number){
                    $sql = "INSERT INTO gazx_personal_accounts (personal_account, kvc_number) values('$personal_account', $kvc_number) ON DUPLICATE KEY UPDATE  personal_account = '$personal_account'";
                    $c = $modx->exec($sql);
                    if($c){ $count += $c; }
                }
                $row++;
            }
            $offset = $offset + 100;
        }else{
            $offset = false;
        }
        
        fclose($handle);
        $data = array('count' => $count, 'offset' => $offset, 'filename' => $_POST['filename']);
        
        return $AjaxForm->success($scriptProperties['successMessage'], $data); 
    }else{
         return  $AjaxForm->error('Проблемы с файлом', array());
    }
}else{
    return  $AjaxForm->error($scriptProperties['validationErrorMessage'], $response['data']);
}
Поскольку я слал запросы через ajaxform, то есть куски с валидацией, но думаю основная суть ясна. Надеюсь услышать мнения более опытных коллег, а ещё лучше варианты решения.
Артур Шевченко
17 марта 2022, 00:26
modx.pro
323
0
Поблагодарить автора Отправить деньги

Комментарии: 3

vectorserver
22 марта 2022, 10:25
+1
Можно вот так:
<?php
$mysqli = new mysqli("localhost","my_user","my_password","my_db");
$csv = '/path/to/file.csv';
$table_name = 'table_name';
$sql = "LOAD DATA LOCAL INFILE '{$csv}' 
        REPLACE INTO TABLE {$table_name} FIELDS TERMINATED BY ',' 
        ENCLOSED BY '\"' LINES TERMINATED BY '\r\n' IGNORE 1 LINES";
$result = $mysqli->query($sql);
Константин Ильин
22 марта 2022, 15:54
+1
Сам пользуюсь приемом ajax запросов. Использовать ajaxForm — слишком ограничений много, можно проще:
1. создаем страничку, там кнопку старт
2. js, ну для быстроты сборки конечно jquery
3. Пишем запрос по нажатию кнопки ".startProcessing"
const urlRequests = '/ajaxQuester/request.php';

function requestToHandler(data = {}, btn = '') {

    $.ajax({
        dataType: 'json',
        type: 'POST',
        url: urlRequests,
        data: {
            action: 'Processing',
            data: data,
        },
        success: function( r) {
            console.log( r);
            if(r.success){

                if(r.data.done){
                    //Здесь делаем что-то по завершению
                    removeLoader(btn,1);

                }else {
                    //если обработка не завершена вызываем заново
                    // так же можно выводить на страницу информацию(лог), что сделал какой этап и offset
                    requestToHandler(r.data,btn);
                }

            }else{
                removeLoader(btn);
                console.log('err');
            }
        }
    });
}

$(document).ready(function() {
    $(document).on('click', '.startProcessing', function (e) {
        requestToHandler({'offset' : 0,'firstQuery' : 1}, $(this));
        addLoader($(this),'',1)
        e.preventDefault();
    })
});

4. создаем request.php в нем обрабатываем, что посылает ajax. Типа того(код не 100% рабочий)
// тут всякие проверки и подключение к modx api
.....

switch ($_POST['action']){

    /*
        Processing
    */
    case 'Processing':

        if(empty($_POST['data']) || !isset($_POST['data']['offset'])){
            echo returnError('no Processing', $resp);return;
        }

        $resp['data'] = $_POST['data'];
        $data = &$resp['data'];

        $offset = $data['offset'];

        $arrayObjects = [];
        $arrayObjects = $testArr;

        if($data['firstQuery']){
            $data['totalObjects'] = count($arrayObjects);
            $data['firstQuery'] = 0;
        }

        $done = false;
        $steps = 35;
        if($data['totalObjects'] < $steps + $offset){
            $steps = $data['totalObjects'] - $offset;
            $done = true;
        }


        //здесь цикл обработки


        $data['done'] = $done;
        $data['offset'] += $steps;


        if($done){
            $resp['text'] .= '<div class="text-success ">Успешно завершено</div>';
        }else{
            $resp['text'] .= '<div class="text-secondary ">Выполнено '.$data['offset']. 'из '.$data['totalObjects'].'</div>';
        }

        echo json_encode($resp);return;
    break;
}
Преимущество перед ajaxForm в том, что как угодно можно построить лог на самой странице например
«Выполнено 7847 из 270000»
    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
    3