19

Say, we have multiple rows to be inserted in a table:

$rows = [(1,2,3), (4,5,6), (7,8,9) ... ] //[ array of values ];

Using PDO:

$sql = "insert into `table_name` (col1, col2, col3) values (?, ?, ?)" ;

Now, how should you proceed in inserting the rows? Like this?

$stmt = $db->prepare($sql);

foreach($rows as $row){
  $stmt->execute($row);
}

or, like this?

$sql = "insert into `table_name` (col1, col2, col3) values ";
$sql .= //not sure the best way to concatenate all the values, use implode?
$db->prepare($sql)->execute();

Which way would be faster and safer? What is the best way to insert multiple rows?

worldofjr
  • 3,868
  • 8
  • 37
  • 49
user3330840
  • 6,143
  • 7
  • 26
  • 39
  • Batch inserts are always faster. Just put the values as '(col1,col2,col3),(col1,col2,col3),...' – ethrbunny Oct 19 '14 at 22:47
  • What about the security risks? Can you just implode the rows like: values (implode(', ', $rows[0]), (implode(', ', $rows[1]), .... Will the $db->prepare method still quote the values properly if needed? – user3330840 Oct 19 '14 at 22:49
  • I'm not clear what your issue with safety is. If you want to insert lots of rows in the most expedient fashion do it in a batch. If you're worried about having the data accepted then either vet it first or insert and handle any exceptions. – ethrbunny Oct 19 '14 at 22:51
  • I meant something like sql injection. If you raw array of rows from user, can you just implode it and send it to db->prepare and execute it? I mean will the prepare method properly escape the fields? – user3330840 Oct 19 '14 at 22:54
  • Dealing with user data is outside the scope of your original question. There are many reliable ways of 'cleaning' input and preventing sql injection. – ethrbunny Oct 19 '14 at 22:56
  • Well, I am not sure how to tweak my question, but, my concern is the security there. I am basically asking what is the best secure and efficient way of inserting multiple rows of raw data into SQL. Or put it another way, what is the best way(performant and elegant) to sanitize/escape multiple rows of raw user input and insert it in batch? – user3330840 Oct 19 '14 at 22:56
  • You can use prepared statements when you add the groups of values in a loop as well. Just add the values to an array and send that array as a paramenter to the `execute()` method: http://php.net/manual/en/pdostatement.execute.php – jeroen Oct 19 '14 at 23:16
  • @Jeroen: I didn't see how you can insert multiple rows in a single insert statement, on that page. So, looks like you need to insert each row in a loop? Or can you bind all the rows in one single statement with a single insert? – user3330840 Oct 20 '14 at 00:09

4 Answers4

18

You have at least these two options:

$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];

$sql = "insert into `table_name` (col1, col2, col3) values (?,?,?)";

$stmt = $db->prepare($sql);

foreach($rows as $row)
{
    $stmt->execute($row);
}

OR:

$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];

$sql = "insert into `table_name` (col1, col2, col3) values ";

$paramArray = array();

$sqlArray = array();

foreach($rows as $row)
{
    $sqlArray[] = '(' . implode(',', array_fill(0, count($row), '?')) . ')';

    foreach($row as $element)
    {
        $paramArray[] = $element;
    }
}

// $sqlArray will look like: ["(?,?,?)", "(?,?,?)", ... ]

// Your $paramArray will basically be a flattened version of $rows.

$sql .= implode(',', $sqlArray);

$stmt = $db->prepare($sql);

$stmt->execute($paramArray);

As you can see the first version features a lot simpler code; however the second version does execute a batch insert. The batch insert should be faster, but I agree with @BillKarwin that the performance difference will not be noticed in the vast majority of implementations.

Joel Lubrano
  • 606
  • 3
  • 9
8

I would do it the first way, prepare the statement with one row of parameter placeholders, and insert one row at a time with execute.

$stmt = $db->prepare($sql);

foreach($rows as $row){
    $stmt-> execute($row);
}

It's not quite as fast as doing multiple rows in a single insert, but it's close enough that you will probably never notice the difference.

And this has the advantage that it's very easy to work with the code. That's why you're using PHP anyway, for the developer efficiency, not the runtime efficiency.

If you have many rows (hundreds or thousands), and performance is a priority, you should consider using LOAD DATA INFILE.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
4

You can also go this way:

<?php
$qmarks = '(?,?,?)'. str_repeat(',(?,?,?)', count($rows)-1);

$sql = "INSERT INTO `table`(col1,col2,col3) VALUES $qmarks";
$vals = array();
foreach($rows as $row)
    $vals = array_merge($vals, $row);
$db->prepare($sql)->execute($vals);

To be honest, I don't know which one will be faster, all depends on the delay between mysql and the php server.

Iazel
  • 2,296
  • 19
  • 23
0
/* test.php */

<?php
require_once('Database.php');

$obj = new Database();
$table = "test";

$rows = array(
    array(
    'name' => 'balasubramani',
    'status' => 1
    ),
    array(
    'name' => 'balakumar',
    'status' => 1
    ),
    array(
    'name' => 'mani',
    'status' => 1
    )
);

var_dump($obj->insertMultiple($table,$rows));
?>

/* Database.php */
<?php
class Database 
{

    /* Initializing Database Information */

    var $host = 'localhost';
    var $user = 'root';
    var $pass = '';
    var $database = "database";
    var $dbh;

    /* Connecting Datbase */

    public function __construct(){
        try {
            $this->dbh = new PDO('mysql:host='.$this->host.';dbname='.$this->database.'', $this->user, $this->pass);
            //print "Connected Successfully";
        } 
        catch (PDOException $e) {
            print "Error!: " . $e->getMessage() . "<br/>";
            die();
        }
    }
/* Insert Multiple Rows in a table */

    public function insertMultiple($table,$rows){

        $this->dbh->beginTransaction(); // also helps speed up your inserts.
        $insert_values = array();
        foreach($rows as $d){
            $question_marks[] = '('  . $this->placeholders('?', sizeof($d)) . ')';
            $insert_values = array_merge($insert_values, array_values($d));
            $datafields = array_keys($d);
        }

        $sql = "INSERT INTO $table (" . implode(",", $datafields ) . ") VALUES " . implode(',', $question_marks);

        $stmt = $this->dbh->prepare ($sql);
        try {
            $stmt->execute($insert_values);
        } catch (PDOException $e){
            echo $e->getMessage();
        }
        return $this->dbh->commit();
    }

    /*  placeholders for prepared statements like (?,?,?)  */

    function placeholders($text, $count=0, $separator=","){
        $result = array();
        if($count > 0){
            for($x=0; $x<$count; $x++){
                $result[] = $text;
            }
        }

        return implode($separator, $result);
    }

}
?>

The above code should be good solution for Inserting Multiple Records using PDO.

sonofkrish
  • 61
  • 1
  • 4
  • 1
    Please add some explanations to your post. Otherwise the answer may be deleted. – Alexander Trakhimenok Nov 20 '17 at 13:52
  • Thank you for this code snippet, which might provide some limited, immediate help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its long-term value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – Toby Speight Nov 20 '17 at 14:28
  • this is basically an implementation of this answer, https://stackoverflow.com/a/2098689/285587 with little added value – Your Common Sense Nov 21 '17 at 09:24