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