133

I'm passing a large dataset into a MySQL table via PHP using insert commands and I'm wondering if it's possible to insert approximately 1000 rows at a time via a query other than appending each value on the end of a mile-long string and then executing it. I am using the CodeIgniter framework so its functions are also available to me.

Dharman
  • 21,838
  • 18
  • 57
  • 107
toofarsideways
  • 3,766
  • 2
  • 27
  • 48

13 Answers13

240

Assembling one INSERT statement with multiple rows is much faster in MySQL than one INSERT statement per row.

That said, it sounds like you might be running into string-handling problems in PHP, which is really an algorithm problem, not a language one. Basically, when working with large strings, you want to minimize unnecessary copying. Primarily, this means you want to avoid concatenation. The fastest and most memory efficient way to build a large string, such as for inserting hundreds of rows at one, is to take advantage of the implode() function and array assignment.

$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));

The advantage of this approach is that you don't copy and re-copy the SQL statement you've so far assembled with each concatenation; instead, PHP does this once in the implode() statement. This is a big win.

If you have lots of columns to put together, and one or more are very long, you could also build an inner loop to do the same thing and use implode() to assign the values clause to the outer array.

NikiC
  • 95,987
  • 31
  • 182
  • 219
staticsan
  • 28,233
  • 4
  • 55
  • 72
  • 5
    Thanks for that! Btw your missing a closing bracket on the end of the function if anyone is planning on copying it. mysql_real_query('INSERT INTO table VALUES (text, category) '.implode(','. $sql)); – toofarsideways Apr 23 '09 at 02:21
  • 3
    Thanks! Fixed. (I often do that...) – staticsan Apr 23 '09 at 02:30
  • I also just noticed, me being silly too the implode(','. $sql)); should be implode(',', $sql)); – toofarsideways Apr 23 '09 at 03:39
  • 1
    and the query should really be 'INSERT INTO table (text, category) VALUES '.implode(','. $sql)' sigh 4am coding leads to terrible debugging :( – toofarsideways Apr 23 '09 at 03:44
  • 1
    @staticsan: Thanks a lot! Superb Technique! Had a really significant speedup! – Legend Sep 13 '10 at 04:32
  • 1
    @staticsan `.implode(',', $sql)` what is the the two quotes and commas for? – Dasa Oct 11 '11 at 12:10
  • @Dasa It puts a comma between each entry in the array as it converts it to a string. The `INSERT` statement requires a comma between each clause, but not after the last. This is the most elegant way to do it. – staticsan Oct 12 '11 at 00:32
  • 1
    @staticsan 3 years on, and you've just solved my "elegant way of adding commas between `VALUE`s but not at the end" problem. Thanks! – msanford Jul 30 '12 at 20:20
  • 1
    one must pay attention to `max_allowed_packet` mysql variable, if the insert is too big an error will occur – Marius.C Jan 21 '14 at 10:07
  • 3
    I believe this code will create a solution for my latest project. My question here is, is this safe from SQL injection? My plans are to switch out `mysql_real_escape_string` with `mysqli_real_escape_string` and `mysql_query` with `mysqli_query`as I'm using MySQLi and these have been deprecated as of PHP5. Many thanks! – wordman Jan 24 '14 at 18:11
  • But if you set large data aray you will get mysql server gone away because large data will be bigger than max_allowed_packet in /etc/mysql/my.cnf – mindsupport Jun 16 '14 at 15:41
  • @mindsupport Yes, you will need to manage the maximum statement size. In practice, even a 1Mb packet-size is an **enormous** statement. It's pretty easy to figure out how many elements that might be and to partition your data. – staticsan Jun 18 '14 at 04:45
  • Hii @staticsan... am trying your answer, its working fine.. but my problem is i have four arrays to be inserted into DB... so how can i edit this code to insert values of multiple arrays into DB ?? Can you please give me a suggestion. – phpfresher May 13 '15 at 12:25
  • 1
    This query is vulnerable to sql injection! – Hedeshy Dec 09 '15 at 09:50
  • Take into account that there is a limit on number of characters (by default mysql 1mb) – Friso Kluitenberg Aug 18 '16 at 11:14
  • Yes - that's what the comment about `max_allowed_packet` refers to. – staticsan Aug 19 '16 at 01:02
  • 3
    `mysql_*` has been removed from PHP, so be sure to use the `mysqli_*` interface. – Rick James Nov 03 '18 at 16:37
63

Multiple insert/ batch insert is now supported by 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')
Dharman
  • 21,838
  • 18
  • 57
  • 107
Somnath Muluk
  • 46,917
  • 28
  • 204
  • 217
  • 2
    I think this is the most recommended way of doing multi row insert rather using mysql_query. Because when we use a framework it is a good practice to always use built-in features of the framework. – Praneeth Nidarshan Jan 24 '18 at 01:57
22

You could prepare the query for inserting one row using the mysqli_stmt class, and then iterate over the array of data. Something like:

$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();

Where 'idsb' are the types of the data you're binding (int, double, string, blob).

Espresso_Boy
  • 1,946
  • 1
  • 15
  • 8
  • 7
    I recently ran some benchmarks comparing bulk insert and prepared insert statements as mentioned here. For around 500 inserts, the prepared inserts method completed in between 2.6-4.4 secs, and the bulk insert method in 0.12-0.35 secs. I would have thought mysql would have "bulked" these prepared statements together and perform just as well as the bulk inserts, but in a default set-up, the performance difference is huge apparently. (Btw all benchmarked queries were running inside a single transaction for each test, as to prevent auto-committing) – Motin Jul 27 '11 at 16:53
15

mysqli in PHP 5 is an object with some good functions that will allow you to speed up the insertion time for the answer above:

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

Turning off autocommit when inserting many rows greatly speeds up insertion, so turn it off, then execute as mentioned above, or just make a string (sqlCombined) which is many insert statements separated by semi-colons and multi-query will handle them fine.

Dharman
  • 21,838
  • 18
  • 57
  • 107
Ross Carver
  • 159
  • 1
  • 2
  • This is the error I got using your idea: "Fatal error: Call to a member function autocommit() on null in /homepages/25/d402746174/htdocs/MoneyMachine/saveQuotes.php on line 30" – user3217883 Feb 13 '18 at 15:07
8

You could always use mysql's LOAD DATA:

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

to do bulk inserts rather than using a bunch of INSERT statements.

vezult
  • 4,915
  • 21
  • 41
  • I had looked into that but I need to manipulate the data before inserting it. It's been given to me as a Cartesian product of a 1400 by 1400 set of int values many of which are zero. I need to convert that to a many to many relationship using an intermediary table to save space hence the need for inserts as opposed to a bulk insert – toofarsideways Apr 23 '09 at 02:26
  • You can always generate a csv file after manipulating it and calling the mysql statement that loads the data – Alexander Jardim Jun 28 '13 at 18:55
  • I think it's helpful to know that the path is local to your SQL client, and not on the SQL server. The file is uploaded to the server and then read by it. I thought the file had to already be on the server, which is not the case. If it is already on the server, remove the `LOCAL` bit. – Kyle Dec 12 '17 at 21:22
4

Well, you don't want to execute 1000 query calls, but doing this is fine:

$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);

Depending on your data source, populating the array might be as easy as opening a file and dumping the contents into an array via file().

bdl
  • 1,424
  • 12
  • 15
  • 1
    It's cleaner if you move that if above the query and change it to something like if ($k>0). – cherouvim Jun 02 '10 at 06:01
  • @cherouvim... Well, you're right about that. Thanks for your input. As I'm re-reading the example I provided,I'm failing to see your point. Care to elaborate (via pastebin, etc?). Thanks- – bdl Jun 03 '10 at 14:19
2
$query= array(); 
foreach( $your_data as $row ) {
    $query[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $query));
Nikunj Dhimar
  • 1,858
  • 14
  • 20
1

You can do it with several ways in codeigniter e.g.

First By loop

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

Second -- By insert batch

$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);

Third way -- By multiple value pass

$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));
Kumar Rakesh
  • 2,638
  • 2
  • 15
  • 38
1

Although it is too late to answer this question. Here are my answer on the same.

If you are using CodeIgniter then you can use inbuilt methods defined in query_builder class.

$this->db->insert_batch()

Generates an insert string based on the data you supply, and runs the query. You can either pass an array or an object to the function. Here is an example using an array:

$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')

The first parameter will contain the table name, the second is an associative array of values.

You can find more details about query_builder here

Jsowa
  • 4,188
  • 4
  • 23
  • 35
Abhishek Singh
  • 499
  • 4
  • 13
0

I have created a class that performs multi-line that is used as follows:

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

where the class is defined as follows:

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;
    }
}
0

Use insert batch in codeigniter to insert multiple row of data.

$this->db->insert_batch('tabname',$data_array); // $data_array holds the value to be inserted
aish
  • 583
  • 1
  • 5
  • 11
0

I had to INSERT more than 14000 rows into a table and found that line for line with Mysqli prepared statements took more than ten minutes, while argument unpacking with string parameters for the same Mysqli prepared statements did it in less than 10 seconds. My data was very repetitive as it was multiples of id's and one constant integer.

10 minutes code:

            $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 second code:

            $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();
Hmerman6006
  • 606
  • 6
  • 17
-1

I have created this simple function which you guys can use easily. You will need to pass the table-name ($tbl), table-field ($insertFieldsArr) against your inserting data, data array ($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));
}
bofredo
  • 2,310
  • 6
  • 30
  • 49
Waqas
  • 144
  • 1
  • 9