Как вставить несколько строк из массива с помощью фреймворка CodeIgniter?

Я передаю большой набор данных в таблицу MySQL через PHP, используя команды вставки, и мне интересно, можно ли вставлять примерно 1000 строк за раз с помощью запроса, кроме добавления каждого значения в конец строки длиной в милю и затем выполняя его. Я использую фреймворк CodeIgniter, поэтому его функции также доступны мне.


person toofarsideways    schedule 23.04.2009    source источник
comment
Я дал ответ в соответствии с вашим вопросом для многострочной вставки Codeigniter.   -  person Somnath Muluk    schedule 19.12.2012
comment
@SomnathMuluk Спасибо, но мне уже давно не нужно было отвечать на этот вопрос :) ...   -  person toofarsideways    schedule 19.12.2012
comment
Я бы рекомендовал использовать функцию Insert_batch в CodeIgniter. Если вы используете библиотеку, всегда старайтесь использовать ее сильные стороны и стандарты кодирования.   -  person Dewald Els    schedule 30.01.2017
comment
Я считаю, что пакет вставки - лучший способ сделать это, см. Ссылку stackoverflow.com/questions / 27206178 / codeigniter-insert-batch   -  person Syed Amir Bukhari    schedule 09.01.2018


Ответы (12)


Сборка одного оператора INSERT с несколькими строками намного быстрее в MySQL, чем один оператор INSERT на строку.

Тем не менее, похоже, что вы можете столкнуться с проблемами обработки строк в PHP, что на самом деле является проблемой алгоритма, а не языка. По сути, при работе с большими строками вы хотите минимизировать ненужное копирование. В первую очередь это означает, что вы хотите избежать конкатенации. Самый быстрый и эффективный с точки зрения памяти способ построить большую строку, например, для вставки сотен строк за одну, - это воспользоваться функцией implode() и назначением массива.

$sql = array(); 
foreach( $data as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $sql));

Преимущество этого подхода состоит в том, что вы не копируете и не повторно копируете оператор SQL, который вы до сих пор собрали, с каждой конкатенацией; вместо этого PHP делает это один раз в операторе implode(). Это большая победа.

Если у вас есть много столбцов, которые нужно собрать, и один или несколько очень длинные, вы также можете построить внутренний цикл, чтобы сделать то же самое, и использовать implode() для присвоения предложения значений внешнему массиву.

person staticsan    schedule 23.04.2009
comment
Спасибо за это! Кстати, вам не хватает закрывающей скобки в конце функции, если кто-то планирует ее скопировать. mysql_real_query ('ВСТАВИТЬ ЗНАЧЕНИЯ таблицы (текст, категория)' .implode (','. $ sql)); - person toofarsideways; 23.04.2009
comment
Спасибо! Фиксированный. (Я часто так делаю ...) - person staticsan; 23.04.2009
comment
Я также только что заметил, что я слишком глупый implode (','. $ Sql)); должно быть implode (',', $ sql)); - person toofarsideways; 23.04.2009
comment
и запрос действительно должен быть 'INSERT INTO table (text, category) VALUES' .implode (','. $ sql) 'sigh Кодирование в 4 часа ночи приводит к ужасной отладке :( - person toofarsideways; 23.04.2009
comment
@staticsan: Большое спасибо! Превосходная техника! Было действительно значительное ускорение! - person Legend; 13.09.2010
comment
@staticsan .implode(',', $sql) для чего нужны две кавычки и запятые? - person Dasa; 11.10.2011
comment
@Dasa Он ставит запятую между каждой записью в массиве, поскольку он преобразует ее в строку. Оператор INSERT требует запятой между каждым предложением, но не после последнего. Это самый элегантный способ сделать это. - person staticsan; 12.10.2011
comment
@staticsan 3 года спустя, и вы только что решили мой элегантный способ добавления запятых между VALUEs, но не в конце проблемы. Спасибо! - person msanford; 31.07.2012
comment
нужно обратить внимание на max_allowed_packet переменную mysql, если вставка слишком большая, произойдет ошибка - person Marius.C; 21.01.2014
comment
Я считаю, что этот код создаст решение для моего последнего проекта. У меня вопрос: безопасно ли это от SQL-инъекции? В моих планах заменить mysql_real_escape_string на mysqli_real_escape_string и mysql_query на mysqli_query, поскольку я использую MySQLi, и они устарели с PHP5. Большое спасибо! - person wordman; 24.01.2014
comment
Но если вы установите большие данные aray, вы потеряете сервер mysql, потому что большие данные будут больше, чем max_allowed_packet в /etc/mysql/my.cnf - person mindsupport; 16.06.2014
comment
@mindsupport Да, вам нужно будет управлять максимальным размером выписки. На практике даже размер пакета 1 Мбайт - это огромное заявление. Довольно легко определить, сколько элементов может быть, и разбить данные на разделы. - person staticsan; 18.06.2014
comment
Привет @staticsan ... я пытаюсь ответить, он работает нормально ... но моя проблема в том, что у меня есть четыре массива, которые нужно вставить в БД ... так как я могу отредактировать этот код, чтобы вставить значения нескольких массивов в БД ?? Не могли бы вы дать мне предложение. - person phpfresher; 13.05.2015
comment
Этот запрос уязвим для внедрения sql! - person Hedeshy; 09.12.2015
comment
Учтите, что есть ограничение на количество символов (по умолчанию mysql 1mb) - person Friso Kluitenberg; 18.08.2016
comment
Да - это то, к чему относится комментарий о max_allowed_packet. - person staticsan; 19.08.2016
comment
mysql_* был удален из PHP, поэтому обязательно используйте mysqli_* интерфейс. - person Rick James; 03.11.2018

CodeIgniter теперь поддерживает множественную / пакетную вставку.

$data = array(
   array(
      'title' => 'My title' ,
      'name' => 'My Name' ,
      'date' => 'My date'
   ),
   array(
      'title' => 'Another title' ,
      'name' => 'Another Name' ,
      'date' => 'Another date'
   )
);

$this->db->insert_batch('mytable', $data);

// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'), ('Another title', 'Another name', 'Another date')
person Somnath Muluk    schedule 21.03.2012
comment
Я думаю, что это наиболее рекомендуемый способ вставки нескольких строк, а не mysql_query. Потому что, когда мы используем фреймворк, рекомендуется всегда использовать встроенные функции фреймворка. - person Praneeth Nidarshan; 24.01.2018

Вы можете подготовить запрос для вставки одной строки с помощью класса mysqli_stmt, а затем выполнить итерацию по массиву данных. Что-то вроде:

$stmt =  $db->stmt_init();
$stmt->prepare("INSERT INTO mytbl (fld1, fld2, fld3, fld4) VALUES(?, ?, ?, ?)");
foreach($myarray as $row)
{
    $stmt->bind_param('idsb', $row['fld1'], $row['fld2'], $row['fld3'], $row['fld4']);
    $stmt->execute();
}
$stmt->close();

Где «idsb» - это типы связываемых данных (int, double, string, blob).

person Espresso_Boy    schedule 23.04.2009
comment
Недавно я провел несколько тестов, сравнивающих массовую вставку и подготовленные операторы вставки, как упоминалось здесь. Примерно для 500 вставок метод подготовленных вставок завершается за 2,6–4,4 секунды, а метод объемной вставки - за 0,12–0,35 секунды. Я бы подумал, что mysql объединит эти подготовленные операторы вместе и будет работать так же хорошо, как и массовые вставки, но при настройке по умолчанию разница в производительности, очевидно, огромна. (Кстати, все проверенные запросы выполнялись внутри одной транзакции для каждого теста, чтобы предотвратить автоматическую фиксацию) - person Motin; 27.07.2011

mysqli в PHP 5 - это объект с некоторыми хорошими функциями, которые позволят вам ускорить время вставки для ответа выше:

$mysqli->autocommit(FALSE);
$mysqli->multi_query($sqlCombined);
$mysqli->autocommit(TRUE);

Отключение автоматической фиксации при вставке большого количества строк значительно ускоряет вставку, поэтому отключите его, затем выполните, как указано выше, или просто создайте строку (sqlCombined), состоящую из множества операторов вставки, разделенных точками с запятой, и с несколькими запросами они будут обрабатываться нормально.

person Ross Carver    schedule 06.03.2011
comment
Это ошибка, которую я получил, используя вашу идею: Неустранимая ошибка: вызов функции-члена autocommit () с нулевым значением в /homepages/25/d402746174/htdocs/MoneyMachine/saveQuotes.php в строке 30 - person user3217883; 13.02.2018

Вы всегда можете использовать mysql LOAD DATA:

LOAD DATA LOCAL INFILE '/full/path/to/file/foo.csv' INTO TABLE `footable` FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n' 

выполнять массовую вставку, а не использовать кучу операторов INSERT.

person vezult    schedule 23.04.2009
comment
Я изучал это, но мне нужно манипулировать данными, прежде чем вставлять их. Он был дан мне как декартово произведение набора значений int 1400 на 1400, многие из которых равны нулю. Мне нужно преобразовать это в отношение «многие ко многим», используя промежуточную таблицу, чтобы сэкономить место, поэтому нужны вставки, а не массовая вставка. - person toofarsideways; 23.04.2009
comment
Вы всегда можете сгенерировать файл csv после манипулирования им и вызова оператора mysql, который загружает данные. - person Alexander Jardim; 28.06.2013
comment
Я думаю, полезно знать, что путь является локальным для вашего SQL-клиента, а не для SQL-сервера. Файл загружается на сервер и затем читается им. Я думал, что файл уже должен быть на сервере, но это не так. Если он уже находится на сервере, удалите бит LOCAL. - person Kyle; 13.12.2017

Что ж, вы не хотите выполнять 1000 вызовов запросов, но это нормально:

$stmt= array( 'array of statements' );
$query= 'INSERT INTO yourtable (col1,col2,col3) VALUES ';
foreach( $stmt AS $k => $v ) {
  $query.= '(' .$v. ')'; // NOTE: you'll have to change to suit
  if ( $k !== sizeof($stmt)-1 ) $query.= ', ';
}
$r= mysql_query($query);

В зависимости от вашего источника данных заполнение массива может быть таким же простым, как открытие файла и выгрузка содержимого в массив через file().

person bdl    schedule 23.04.2009
comment
Будет чище, если вы переместите это if над запросом и измените его на что-то вроде if ($ k ›0). - person cherouvim; 02.06.2010
comment
@cherouvim ... Ну, в этом ты прав. Спасибо за ваш вклад. Перечитывая приведенный мной пример, я не понимаю вашей точки зрения. Позаботьтесь о разработке (через pastebin и т. Д.?). Спасибо- - person bdl; 03.06.2010

Вы можете сделать это несколькими способами в codeigniter, например.

Первый по циклу

foreach($myarray as $row)
{
   $data = array("first"=>$row->first,"second"=>$row->sec);
   $this->db->insert('table_name',$data);
}

Второй - путем вставки пакета

$data = array(
       array(
          'first' => $myarray[0]['first'] ,
          'second' => $myarray[0]['sec'],
        ),
       array(
          'first' => $myarray[1]['first'] ,
          'second' => $myarray[1]['sec'],
        ),
    );

    $this->db->insert_batch('table_name', $data);

Третий способ - передача нескольких значений

$sql = array(); 
foreach( $myarray as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['first']).'", '.$row['sec'].')';
}
mysql_query('INSERT INTO table (first, second) VALUES '.implode(',', $sql));
person Kumar Rakesh    schedule 22.08.2016
comment
Какой из этих трех самый быстрый? - person DropHit; 23.01.2021
comment
Кстати, mysql_query работает быстро, но, как вы знаете, этот тип кодирования является частью CORE PHP, а не фреймворком. Поэтому вам нужно использовать метод insert_batch в codigniter. - person Kumar Rakesh; 02.02.2021

Хотя уже поздно отвечать на этот вопрос. Вот мой ответ на то же самое.

Если вы используете CodeIgniter, вы можете использовать встроенные методы, определенные в классе query_builder.

$ this- ›db-› insert_batch ()

Создает строку вставки на основе предоставленных вами данных и выполняет запрос. Вы можете передать в функцию массив или объект. Вот пример использования массива:

$data = array(
    array(
            'title' => 'My title',
            'name' => 'My Name',
            'date' => 'My date'
    ),
    array(
            'title' => 'Another title',
            'name' => 'Another Name',
            'date' => 'Another date'
    )
);

$this->db->insert_batch('mytable', $data);
// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'),  ('Another title', 'Another name', 'Another date')

Первый параметр будет содержать имя таблицы, второй - ассоциативный массив значений.

Дополнительную информацию о query_builder можно найти здесь

person Abhishek Singh    schedule 16.04.2017

Я создал класс, который выполняет многострочность и используется следующим образом:

$pdo->beginTransaction();
$pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
$pmi->insertRow($data);
// ....
$pmi->insertRow($data);
$pmi->purgeRemainingInserts();
$pdo->commit();

где класс определяется следующим образом:

class PDOMultiLineInserter {
    private $_purgeAtCount;
    private $_bigInsertQuery, $_singleInsertQuery;
    private $_currentlyInsertingRows  = array();
    private $_currentlyInsertingCount = 0;
    private $_numberOfFields;
    private $_error;
    private $_insertCount = 0;

    /**
     * Create a PDOMultiLine Insert object.
     *
     * @param PDO $pdo              The PDO connection
     * @param type $tableName       The table name
     * @param type $fieldsAsArray   An array of the fields being inserted
     * @param type $bigInsertCount  How many rows to collect before performing an insert.
     */
    function __construct(PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
        $this->_numberOfFields = count($fieldsAsArray);
        $insertIntoPortion = "REPLACE INTO `$tableName` (`".implode("`,`", $fieldsAsArray)."`) VALUES";
        $questionMarks  = " (?".str_repeat(",?", $this->_numberOfFields - 1).")";

        $this->_purgeAtCount = $bigInsertCount;
        $this->_bigInsertQuery    = $pdo->prepare($insertIntoPortion.$questionMarks.str_repeat(", ".$questionMarks, $bigInsertCount - 1));
        $this->_singleInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks);
    }

    function insertRow($rowData) {
        // @todo Compare speed
        // $this->_currentlyInsertingRows = array_merge($this->_currentlyInsertingRows, $rowData);
        foreach($rowData as $v) array_push($this->_currentlyInsertingRows, $v);
        //
        if (++$this->_currentlyInsertingCount == $this->_purgeAtCount) {
            if ($this->_bigInsertQuery->execute($this->_currentlyInsertingRows) === FALSE) {
                $this->_error = "Failed to perform a multi-insert (after {$this->_insertCount} inserts), the following errors occurred:".implode('<br/>', $this->_bigInsertQuery->errorInfo());
                return false;
            }
            $this->_insertCount++;

            $this->_currentlyInsertingCount = 0;
            $this->_currentlyInsertingRows = array();
        }
        return true;
    }

    function purgeRemainingInserts() {
        while ($this->_currentlyInsertingCount > 0) {
            $singleInsertData = array();
            // @todo Compare speed - http://www.evardsson.com/blog/2010/02/05/comparing-php-array_shift-to-array_pop/
            // for ($i = 0; $i < $this->_numberOfFields; $i++) $singleInsertData[] = array_pop($this->_currentlyInsertingRows); array_reverse($singleInsertData);
            for ($i = 0; $i < $this->_numberOfFields; $i++)     array_unshift($singleInsertData, array_pop($this->_currentlyInsertingRows));

            if ($this->_singleInsertQuery->execute($singleInsertData) === FALSE) {
                $this->_error = "Failed to perform a small-insert (whilst purging the remaining rows; the following errors occurred:".implode('<br/>', $this->_singleInsertQuery->errorInfo());
                return false;
            }
            $this->_currentlyInsertingCount--;
        }
    }

    public function getError() {
        return $this->_error;
    }
}
person user3682438    schedule 28.05.2014

Используйте пакет вставки в codeigniter, чтобы вставить несколько строк данных.

$this->db->insert_batch('tabname',$data_array); // $data_array holds the value to be inserted
person aish    schedule 06.03.2015

Мне пришлось ВСТАВИТЬ более 14000 строк в таблицу, и я обнаружил, что эта строка для строки с подготовленными операторами Mysqli заняла более десяти минут, в то время как распаковка аргументов со строковыми параметрами для тех же подготовленных операторов Mysqli сделала это менее чем за 10 секунд. Мои данные были очень повторяющимися, поскольку они были кратными идентификаторам и одному постоянному целому числу.

10-минутный код:

            $num = 1000;
            $ent = 4;
            $value = ['id' => 1,
                      'id' => 2,
                      'id' => 3,
                      'id' => 4,
                      'id' => 5,
                      'id' => 6,
                      'id' => 7,
                      'id' => 8,
                      'id' => 9,
                      'id' => 10,
                      'id' => 11,
                      'id' => 12,
                      'id' => 13,
                      'id' => 14];
            $cnt = 0;
            $query = "INSERT INTO table (col1, col2) VALUES (?,?)";
            $stmt = $this->db->prepare($query);
            $stmt->bind_param('ii', $arg_one,$arg_two);
                    foreach ($value as $k => $val) {
                         for ($i=0; $i < $num; $i++) { 
                            $arg_one = $k;
                            $arg_two = $ent;
                            if($stmt->execute()) {
                                $cnt++;
                            }
                        }
                    }

10-секундный код:

            $ent = 4;
            $num = 1000;
            $value = ['id' => 1,
                      'id' => 2,
                      'id' => 3,
                      'id' => 4,
                      'id' => 5,
                      'id' => 6,
                      'id' => 7,
                      'id' => 8,
                      'id' => 9,
                      'id' => 10,
                      'id' => 11,
                      'id' => 12,
                      'id' => 13,
                      'id' => 14];
             $newdat = [];
             foreach ($value as $k => $val) {
                 for ($i=0; $i < $num; $i++) {
                    $newdat[] = $val;
                    $newdat[] = $ent;
                 }
             }
            // create string of data types
            $cnt = count($newdat);
            $param = str_repeat('i',$cnt);
            // create string of question marks
            $rec = (count($newdat) == 0) ? 0 : $cnt / 2 - 1;
            $id_q = str_repeat('(?,?),', $rec) . '(?,?)';
            // insert
            $query = "INSERT INTO table (col1, col2) VALUES $id_q";
            $stmt = $db->prepare($query);
            $stmt->bind_param($param, ...$newdat);
            $stmt->execute();
person Hmerman6006    schedule 24.01.2021

Я создал эту простую функцию, которую вы, ребята, можете легко использовать. Вам нужно будет передать имя таблицы ($tbl), поле таблицы ($insertFieldsArr) вместо ваших данных вставки, массива данных ($arr).

insert_batch('table',array('field1','field2'),$dataArray);

    function insert_batch($tbl,$insertFieldsArr,$arr){ $sql = array(); 
    foreach( $arr as $row ) {
        $strVals='';
        $cnt=0;
        foreach($insertFieldsArr as $key=>$val){
            if(is_array($row)){
                $strVals.="'".mysql_real_escape_string($row[$cnt]).'\',';
            }
            else{
                $strVals.="'".mysql_real_escape_string($row).'\',';
            }
            $cnt++;
        }
        $strVals=rtrim($strVals,',');
        $sql[] = '('.$strVals.')';
    }

    $fields=implode(',',$insertFieldsArr);
    mysql_query('INSERT INTO `'.$tbl.'` ('.$fields.') VALUES '.implode(',', $sql));
}
person Waqas    schedule 14.12.2016