1

I'm using code similar to the one below to insert into a mysql DB (using PDO.) The aim was to insert data using only one query. Unfortunately it defeats the purpose of using PDO because it doesn't make use of bindValue or param. I want to write the code so that it is safer but still avoids unnecessary queries. Can anyone recommend how to do this?

NOTE1: The number of questions in the set can change each time the program is accessed (ie $totalQ can be different each time it is run).

try 
{
    for ($i=0; $i<$totalQ; $i++)
    { 
        $stqid[$i][0]=$lastInsertValue;     //instance         PDO::PARAM_INT
        $stqid[$i][1]=$jqid[$i][0];          //question number  PDO::PARAM_INT
        $stqid[$i][2]=$jqid[$i][5];          //result           PDO::PARAM_INT
        $stqid[$i][3]=$jqid[$i][3];          //question start   PDO::PARAM_STR 
        $stqid[$i][4]=$jqid[$i][4];          //question finish  PDO::PARAM_STR 
    }

    $values = array();
    foreach ($stqid as $rowValues) 
    {
        foreach ($rowValues as $key => $rowValue) 
        {
            $rowValues[$key] = $rowValues[$key];  
        }

        $values[] = "(" . implode(', ', $rowValues) . ")";
    }

    $count = $dbh->exec("INSERT INTO results(instance, qid, result, start, finish) VALUES  ".implode (', ', $values));  
    $dbh = null;
}

NOTE2: Commas in the time format for start and finish can cause errors with the implode statement. I've just added them so you can see what I'm trying to achieve.

Any help would be much appreciated. Thanks.

EDIT: While I chose chris' answer, I'm very grateful to Alix Axel for his advice. It helped a lot, thank you!

TryHarder
  • 2,704
  • 8
  • 47
  • 65
  • 2
    You can add as much placeholders as you need and then bind values to them – zerkms May 10 '12 at 04:39
  • 1
    Why not just use a prepared statement and just execute it with different values? I haven't tested this, but I don't think the performance impact should be that dramatic. I may be wrong tho. – Lior Cohen May 10 '12 at 04:41
  • I really struggle with PDO. Could you possibly link to an example? – TryHarder May 10 '12 at 04:45
  • Possible duplicate: http://stackoverflow.com/questions/1176352/pdo-prepared-inserts-multiple-rows-in-single-query – Ja͢ck May 10 '12 at 04:52

2 Answers2

2

How about something like this:

try 
{
    for ($i=0; $i<$totalQ; $i++)
    { 
        $stqid[$i][0]=$lastInsertValue;     //instance         PDO::PARAM_INT
        $stqid[$i][1]=$jaid[$i][0];          //question number  PDO::PARAM_INT
        $stqid[$i][2]=$jaid[$i][5];          //result           PDO::PARAM_INT
        $stqid[$i][3]=$jqid[$i][3];          //question start   PDO::PARAM_STR 
        $stqid[$i][4]=$jqid[$i][4];          //question finish  PDO::PARAM_STR 
    }

    $values = null;

    foreach ($stqid as $rowValues) 
    {
        $values .= vsprintf('(%s, %s, %s, %s, %s) ', array_map(array($dbh, 'quote'), $rowValues));
    }

    $count = $dbh->exec('INSERT INTO results (instance, qid, result, start, finish) VALUES ' . rtrim($values) . ';');  
    $dbh = null;
}
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • Thanks for your answer. I'm guessing you used vsprintf to avoid the errors associated with commas in the time format for start and finish. Does this make it safer though? – TryHarder May 10 '12 at 05:00
  • 1
    @moomoochoo: No, I used `[v]sprintf` because you always have 5 placeholders and it's way more reliable than imploding by `,`. Regarding the safety, indeed, it's pretty safe: first `PDO::quote()` comes into action for all arguments (this is enough to stop any SQL injection attack), then `vsprintf` casts the first 3 arguments to integers. Pretty much bulletproof. – Alix Axel May 10 '12 at 05:03
  • Thank you for the detailed explanation! I understand it much better now. – TryHarder May 10 '12 at 05:08
  • I'm not sure why, but if I echo out $values prior to `$dbh-> exec` it reads `values=(0, 0, 0, '2012-05-10 06:11:43', '2012-05-10 06:11:43')` The problem with this is that the first few values should be 1,1, null. I also get error `SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(0, 0, 0, '2012-05-10 06:11:43', '2012-05-10 06:11:43')` Do you have any ideas about what is happening? – TryHarder May 10 '12 at 06:19
  • @moomoochoo: Humm... If the values were integers in the first place `PDO::quote` shouldn't add quotes around them (that's the reason why you're seeing `0` instead of `1`, because `intval("'1'") = 0`). Anyway, using `(%s, %s, %s, %s, %s) ` in the `vsprintf()` call will fix it, without compromising security. – Alix Axel May 10 '12 at 06:26
  • Strange, at least 2 of those values should be integers. Using string %s does however fix the issue with the first 3 values. Thanks again for your help! It is much appreciated. – TryHarder May 10 '12 at 06:49
2

Untested. Still uses prepared statements.

$numColumns = 5; //or $numColumns = count($stqid[0]);
$rowPlaceholder = join(', ', array_fill(0, $numColumns, '?'));
$rowPlaceholders = array_fill(0, $totalQ, "($rowPlaceholder)");
echo $sql = "INSERT INTO results(instance, qid, result, start, finish) VALUES " . join(", \n", $rowPlaceholders);
$flat = call_user_func_array('array_merge', $stqid);
$stmt = $dbh->prepare($sql);
$stmt->execute($flat);
goat
  • 31,486
  • 7
  • 73
  • 96
  • `$flat = call_user_func_array('array_merge', $stqid);` is that right? – Alix Axel May 10 '12 at 05:06
  • Thanks for your answer. This worked for me, but I don't really understand how it makes it safer. Also I found that if a `result` had a value of `null` it was inserted into mysql as 0 (but I guess that is a separate issue!) – TryHarder May 10 '12 at 06:10
  • 1
    @moomoochoo: He's counting how many *N* records you will insert and he generates a string `(?, ?, ?, ?, ?)` repeated *N* times, which is then appended to the SQL query. Then, he flattens all your `$stqid` values and uses that flattened array as the parameter for the prepared query. – Alix Axel May 10 '12 at 06:30
  • Actually this does work with null- I was doing something stupid :X – TryHarder May 10 '12 at 07:21
  • It makes it safer by using prepared statements. That's why i had `echo $sql`, so you could see that sql that was created full of tons of placeholders. – goat May 10 '12 at 14:37
  • OK thanks. I hadn't understood correctly why prepared statements were safe. I had been thinking that we had to specify parameters for each placeholder. I've done some more reading and think I understand now. The prepared statement separates the commands from the values. Thanks guys! – TryHarder May 11 '12 at 03:45