3

I need to insert many rows ( between 150 to 300 ) into MySQL table and I want to know the better of the following approaches in terms of performance:

Approach 1 :

foreach( $persons as $person ){

       $stmt = $dbLink->prepare( "INSERT INTO table SET id = :ID, 
                                                 name = :name, 
                                                 email = :email, 
                                                 mobile = :mobile");
       $stmt->execute( array( ':ID'=>$person->getID(),
                              ':name'=>$person->getName(),
                              ':email'=>$person->getEmail(),
                              ':mobile'=>$person->getMobile(),
                              ) );
}

Approach 2:

$stmt = $dbLink->prepare( "INSERT INTO table SET id = :ID, 
                                                 name = :name, 
                                                 email = :email, 
                                                 mobile = :mobile");

$stmt->bindParam( ':ID', $person->getID(), PDO::PARAM_STR );
$stmt->bindParam( ':name', $person->getName(), PDO::PARAM_STR );
$stmt->bindParam( ':email', $person->getEmail(), PDO::PARAM_STR );
$stmt->bindParam( ':mobile', $person->getMobile(), PDO::PARAM_STR );

foreach( $persons as $person ){

       $stmt->execute(); 
}
Stephen Adelakun
  • 784
  • 2
  • 7
  • 24
  • I don't think there is a significative difference between the two approachs. You are making the same amount of SQL calls to the database. – Amarnasan Dec 11 '15 at 13:02
  • 6
    Beside the small difference, you are running 150 single inserts. Use a single query to insert 150 values instead: http://stackoverflow.com/questions/1176352/pdo-prepared-inserts-multiple-rows-in-single-query (just keep an eye on your max allowed query size, maybe use 3 queries, with 50 values each, or whatever matches best) – dognose Dec 11 '15 at 13:02

4 Answers4

3

It is the amount of calls to the database what makes the difference. Reduce the amount of calls as much as possible.

Instead of this:

insert (a,b,c) values (d,e,f); 
insert (a,b,c) values (g,h,i); 
insert (a,b,c) values (j,k,l); 
insert (a,b,c) values (m,n,o);

do this:

insert (a,b,c) values (d,e,f),(g,h,i),(j,k,l),(m,n,o);

Thus making in one call what you would do in 4 calls.

Amarnasan
  • 14,939
  • 5
  • 33
  • 37
1

To answer to your question, this is the way you should structure your prepare / bind / execute phases:

//prepare the query only the first time
$stmt = $dbLink->prepare( "INSERT table (id, name, email, mobile) 
                           VALUES (:ID, :name, :email, :mobile)" ); 


//bind params and execute for every person   
foreach( $persons as $person ){
    $stmt->bindValue( ':ID', $person->getID(), PDO::PARAM_STR );
    $stmt->bindValue( ':name', $person->getName(), PDO::PARAM_STR );
    $stmt->bindValue( ':email', $person->getEmail(), PDO::PARAM_STR );
    $stmt->bindValue( ':mobile', $person->getMobile(), PDO::PARAM_STR );

    $stmt->execute(); 
}

If you have PDO::ATTR_EMULATE_PREPARES = false, the query will be prepared by mysql only the first time.

In the first case it would be re-prepared for every loop cycle

As correctly other users are saying, remember that a better performance improvement would be to make ONLY one insert instead of many insert in a for loop


EDIT: How to use parameter bindings AND one query

To use parameters' binding and only one query a solution could be:

$placeholders = "";    //this will be filled with placeholders : ( :id_1, :name_1, :email_1, :mobile_1),( :id_2 ... )
$parameters = array(); //this will keep the parameters bindings

$i = 1;
foreach( $persons as $person )
{
    //add comma if not first iteration
    if ( $placeholders )
        $placeholders .= ", ";

    //build the placeholders string for this person
    $placeholders .= "( :id_$i, :name_$i, :email_$i, :mobile_$i )";

    //add parameters for this person
    $parameters[":id_$i"] = $person->getID(); 
    $parameters[":name_$i"] = $person->getName(); 
    $parameters[":email_$i"] = $person->getEmail(); 
    $parameters[":mobile_$i"] = $person->getMobile(); 

    $i++;
}

//build the query
$stmt = $dbLink->prepare( "INSERT INTO table (id, name, email, mobile) 
                           VALUES " . $placeholders );

//execute the query passing parameters
$stmt->execute( $parameters );

In the first part of the loop we build the string $placeholders with a set of placeholders for every person, in the second part of the loop we store the bindings of the values of the placeholders in the $parameters array

At the end of the loop we should have all the placeholders and parameters set, and we can execute the query passing the $parameters array to the execute method. This is an alternative way in respect to use the bindValue / bindParam methods but the result should be the same

I think this is the only way to use parameter bindings AND use only one query

Moppo
  • 18,797
  • 5
  • 65
  • 64
  • Your answer makes the same number of SQL call – Veerendra Dec 11 '15 at 13:15
  • @Moppo, I put my parameter binding outside the loop, you put yours inside the loop, are they the same? I used to think that parameter binding with bindParam should be done once, since it is by reference. – Stephen Adelakun Dec 11 '15 at 13:37
  • @StephenAdelakun : the bindings should go INSIDE the loop as you don't have the `$person` variable available before. Besides, a different binding should be done for every query execution with different parameter values – Moppo Dec 11 '15 at 13:42
  • @Moppo, I understand your point but parameter binding doesn't seem to respect that scoping you are referring to. See some examples here :http://php.net/manual/en/pdo.prepared-statements.php – Stephen Adelakun Dec 11 '15 at 14:06
  • 1
    Learning more about PDO. I was mistaken - `PDO::ATTR_EMULATE_PREPARES` must be set to `false` for the query to be prepared. And the separate execute to be used. When emulation is true then just queries are used. On version PHP 5.3.29 anyway. – Ryan Vincent Dec 11 '15 at 14:07
  • @StephenAdelakun: yes, you should use `as &$person ` or use `bindValue ` instead of `bindParam ` (as i've edited in my answer) – Moppo Dec 11 '15 at 14:12
  • @StephenAdelakun : i've noticed only now that the insert syntax in the prepare statement was wrong, i've just updated it – Moppo Dec 11 '15 at 21:54
  • @Veerendra: no, my first solution is not going to make the same number of SQL calls in respect to the proposed method , as the prepare phase is called only once before the loop, in respect to executing it every time inside the loop – Moppo Dec 11 '15 at 23:09
1

You can use the below code to avoid multiple SQL calls and insert the data in Single SQL call

$first_string = "INSERT INTO table (id, name, email,mobile) VALUES ";//Basic query
foreach( $persons as $person )
{
    $first_string .="(".$person->getID().",".$person->getName().",".$person->getEmail().",".$person->getMobile()."),";//Prepare the values
}

$final_query_string = substr($first_string, 0,-1);// This will remove the extra , at the end of the string

$stmt = $dbLink->prepare($final_query_string);
$stmt->execute(); 

Now execute the final query string prepared.

This way the query is prepared as the string and you need to execute it in one go. This will make a single SQL call

Veerendra
  • 2,562
  • 2
  • 22
  • 39
  • 1
    This solution is not going to use parameters bindings. In this case it would be the same to use `$stmt->query()`, without the prepare and execute phases – Moppo Dec 11 '15 at 13:55
  • @Veerendra : 1 upvote for the terseness. It will be great if someone could produce a version that supports prepared statement. – Stephen Adelakun Dec 11 '15 at 14:22
  • @StephenAdelakun : check the edit in my answer to see if it could be useful – Moppo Dec 11 '15 at 22:59
0
//declare array of values to be passed into PDO::Statemenet::execute()
$values = array();

//prepare sql string
$sql = 'INSERT INTO students ( id, name, email, mobile ) VALUES ';

 foreach( $students as $student ){

      $sql .= '( ?, ?, ?, ? ),';  //concatenate placeholders with sql string  

      //generate array of values and merge with previous values 
      $values = array_merge( $values,  array(  $student->getID(), 
                                               $student->getName(),
                                               $student->getEmail(),                       
                                               $student->getMobile(),
                                            )
                           );
 }

 $sql = rtrim( $sql, ',' );   //remove the trailing comma (,) and replace the sql string
 $stmt = $this->dbLink->prepare( $sql );            
 $stmt->execute( $values );

Full credits to all who have inspired me to arrive at this solution. This seems to be terse and clear:

In particular, the answer of JM4 at PDO Prepared Inserts multiple rows in single query really helped. I also recognise Moppo on this page.

Community
  • 1
  • 1
Stephen Adelakun
  • 784
  • 2
  • 7
  • 24
  • Yes, it uses the same method i've explained, but uses question marks instead of named placeholders...probably my answer deservers at least one upvote :) – Moppo Dec 13 '15 at 11:52
  • @Moppo, yeah I'd really love to give one upvote to your edit but there are two issues with it: – Stephen Adelakun Dec 13 '15 at 20:00
  • @Moppo,I said there are two issues with you edit: 1. `if( $placeholders ))` will be true whether or not it is the first iteration for the declaration `$placeholders = ""` brings `$placeholders` into existence. I think you should have done `if( "" == $placeholders)` instead. 2. `$placeholders` will have a trailing comma in the end, and that will invalidate your query if you do not remove the trailing comma. Well, I had already tested my solution on my local machine before coming online to see your edit. All the same, I'll give the upvote. But please correct these issues if you are convinced. – Stephen Adelakun Dec 13 '15 at 20:14
  • 1. No: in [PHP](http://php.net/manual/en/language.types.boolean.php) `if ( $placeholders )` and `if ( "" == $placeholders)` will both give the same result: `false`. 2. No: the comma is added to the string in every iteration BEFORE adding the placeholders of the current iteration. So in the last loop, the trailing comma is not inserted at the end of the string. In you example, instead, the query will have a trailing comma as you add the comma AFTER the placeholders. You could try the code by yourself to verify that – Moppo Dec 13 '15 at 23:15
  • @Moppo, understood 2, not sure of 1. 1 upvote all the same. – Stephen Adelakun Dec 13 '15 at 23:21
  • Thanks. Yes, for 1 my previous stamement is not correct, as `if ( "" == $placeholders)` will result: `true`. But my example is correct: when `$placeholders = ""` then `if ( $placeholders )` will be `false` and the comma will not be added to the beginning of the string in the first iteration. In the following iterations `$palcehoders` will be a string, `if ( $placeholders )` will be `true` and the comma will be added – Moppo Dec 13 '15 at 23:32