Довольно часто возникает потребность занесения информации из файлов в базу данных. Одним из способов решения данной задачи является: открытие файла, построчно считывать файл, разбивать строку по разделителю и заносить в базу. Но в таком случае может возникнуть проблема в скорости импорта, если файл будет слишком большой.
Для решения данной проблемы можно воспользоваться оператором MySQL LOAD DATA INFILE. LOAD DATA INFILE позволяет загружать информацию из файлов с большой скоростью, что нам и требуется.
Но у этого метода я заметил свои плюсы и минусы:
- — нет возможности отслеживать корректность вводимых данных
- + скорость загрузки впечатляет
Представим ситуацию, что нам нужно выгружать в БД прайс-листы организаций. Для этого понадобится создать форму с возможностью выбора файла и кнопкой для занесения файла в базу.

Для начала создадим таблицу tbl_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.
