Чтение большого файла 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
121
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, 14:55
    0
    Интересный вариант)))
    Константин Ильин
    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