Быстрый импорт файла в базу данных

Довольно часто возникает потребность занесения информации из файлов в базу данных. Одним из способов решения данной задачи является: открытие файла, построчно считывать файл, разбивать строку по разделителю и заносить в базу. Но в таком случае может возникнуть проблема в скорости импорта, если файл будет слишком большой.

Для решения данной проблемы можно воспользоваться оператором MySQL LOAD DATA INFILE. LOAD DATA INFILE позволяет загружать информацию из файлов с большой скоростью, что нам и требуется.

  1. Создание модели
  2. Создание представления
  3. Импорт прайс-листа
  4. Создание контроллера

Но у этого метода я заметил свои плюсы и минусы:

  • — нет возможности отслеживать корректность вводимых данных
  • + скорость загрузки впечатляет

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

q

Для начала создадим таблицу tbl_price, где будем хранить информацию о прайс листе.

структура таблицы price

id_user будет являться внешним ключом для связи организации и прайс-листа.

Создадим файл который будем импортировать

product cost unit group_product comment
опилки0;200;р;древесина;мой коммент;
опилки1;200;р;древесина;мой коммент;
опилки2;200;р;древесина;мой коммент;

Создание модели

Создаем модель для таблицы tbl_price генератором кода gii. Добавляем метод импорта файла в класс модели Price.

//путь для временного хранения прайса, поскольку файл временный, то я позволил себе временно подержать его в папке images, но вы можете поступить более правильно
private $myPath = 'images';

public function import_price($file,$usr_id){

 //поскольку мы считаем что организация может иметь только один прайс, то назовем файл по id организации
 $file->saveAs($this->myPath.'/'.$usr_id);
 $patch = $this->myPath.'/'.$usr_id;

 //создаем sql запрос с необходимыми параметрами
 $sql = "LOAD DATA LOCAL INFILE '".$patch."'
 INTO TABLE tbl_price
 FIELDS TERMINATED BY ';'
 LINES TERMINATED BY '\n'
 IGNORE 1 LINES
 (product,cost,unit,group_product,comment)
 SET date_price = CURRENT_TIMESTAMP, id_user=".$usr_id;

 $connection=Yii::app()->db;
 if ($connection->createCommand($sql)->execute())$rez=true; else $rez=false;

 //удаляем файл после загрузки его в базу
 unlink($patch);
 return $rez;

 }

Разберем подробнее команду LOAD DATA LOCAL INFILE.

LOCAL говорит о том, что файл будет браться из директории клиентского сервера, если же написать просто LOAD DATA INFILE, то файл будет браться из директории сервера SQL.

FIELDS TERMINATED BY говорит о том каким образом отделяются друг от друга поля в нашем случае это «;» точка с запятой.

LINES  TERMINATED BY говорит о то каким образом будут разделяться строки, у нас это символ перехода на новую строку.

IGNORE 1 LINES  количество пропускаемых строк сверху, пропускаем одну поскольку у нас есть шапка, если заголовки столбцов не использовать то можно этот параметр не писать.

Далее в скобках указываем те параметры, которые будут заноситься в базу из файла, ВАЖНО указать ту последовательность столбцов БД, которую указали в файле т.е. если в базе столбцы идут «цена», «кол-во», «ед-изм» а в файле импорта «ед-изм», «цена», «кол-во» то в SQL запросе следует указывать «ед-изм», «цена», «кол-во».

Также при копировании файла в базу данных может возникнуть ситуация, когда в файле импорта хранится не вся необходимая информация. В нашем случае это id организации кому принадлежит прайс и время формирования прайса. Для этого нужно использовать SET где мы присваиваем date_price текущую дату и id_user id организации.

Поскольку корректность вводимых данных в этом способе проверить нельзя (если можно, то поделитесь как), то изменим rules в нашей модели.

public function rules()
 {
  array('price_file', 'file', 'allowEmpty' => false, 'types' => 'csv','on'=>'import_csv','wrongType'=>'Только расширение csv'),
}

Здесь я создал сценарий import_csv, где ведется контроль расширения выбранного пользователем файла. Расширение я выбрал csv, но оно может быть и другим.

Создание представления

Создадим класс представления Price и внесем в него следующий код.


<h2 id="import_price">Импорт прайс-листа</h2>

<div class="form"><?php $form1=$this->beginWidget('CActiveForm', array(
 'id'=>'import_csv',
 'htmlOptions'=>array('enctype'=>'multipart/form-data'),
 'focus'=>array($price,'price_file'),
 )); ?>
 <?php echo CHtml::errorSummary($price); ?>
 <?php echo CHtml::activeFileField($price, 'price_file'); ?>

 <?php echo CHtml::submitButton('Импорт'); ?>
<?php $this->endWidget(); ?></div>

Ну здесь все просто, единственный момент на который стоит обратить внимание это

'htmlOptions'=>array('enctype'=>'multipart/form-data'),

иначе поле activeFileField будет возвращать пустое значение.
импорт прайс-листа

Создание контроллера

Создадим контроллер PriceController и в нем actionImport со следующим кодом.

public function actionImport()
{
 //присваиваем id текущего пользователся
 $usr_id=Yii::app()->user->id;
 //создаем экземпляр класса Price со сценарием import_csv
 $price= new Price('import_csv');

 //проверка была ли кнопка Импорт нажата
 if(isset($_POST['Price']))
 {
   //если кнопка нажата и файл выбран
   if ($file = CUploadedFile::getInstance($price,'price_file'))
   {
     //проводим валидацию полученного файла, если все ОК, то переходим на главную страницу
     $price->price_file=$file;
     if($price->validate() && $price->import_price($file,$usr_id))
     {
      $this->redirect(Yii::app()->homeUrl);
     }
   }
 }
 $this->render('profile',array('price'=>$price));
}

Вот и все! Теперь наше представление доступно по адресу mysite.ru/index.php/price/import или mysite.ru/index.php?r=price /import.

comments powered by HyperComments
nn
2014-05-02 20:46:29
jjjj
Игорь
2015-07-27 16:52:56
Спасибо большое за тему! А неподскажите - в таком способе импорта - есть возможность делать операции перед импортом над вносимыми данными? Например поле для цены надо умножить перед импортом. П.С. НА сколько скорость при таком методе быстрее чем с разбором строк через ПХП
При копировании материалов обратная ссылка на play-stop.ru желательна обязательна!