1

I have three tables in my database (MySQL/InnoDB): pupil, evening_activity and pupil_evening_activity. As you can guess there is an m:m relationship between pupil and evening_activity.

I have a HTML form to insert new pupils and associate to them evening activities. After submitting the form, I would like to insert pupils and their associated activities in the database. So I have written this code:

$db = new PDO('mysql:host=localhost;dbname=school;charset=utf8','root', '****');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);


try {
    $sql = "INSERT INTO pupil 
            (name, surname) 
            VALUES
            ('test_name', 'test_surname')";
$st = $db->prepare($sql);
$st->execute();

    foreach ($eveningActivityIds as $eveningActivityId) { 
    $sql = "INSERT INTO pupil_evening_activity 
                (pupil_id, evening_activity_id) 
                VALUES 
                (?, ?)
        ";
    $st = $db->prepare($sql);
    $st->execute(array($db->lastInsertId(), $eveningActivityId));
    }

} catch (PDOException $e) {

echo $e->getMessage();
return false;

}

I have written that code expecting PDO::lastInsertId() always returns the last inserted pupil's id, but I've found out that what it really returns is the last inserted id on the whole database. So if another row is inserted in a hypothetical teacher table just before calling to $db->lastInsertId() on the code above, $db->lastInsertId() would return the id of the inserted teacher instead of the id of the last pupil.

So how I can get 100% safety the last inserted id of pupil table after submitting the form?

I have thought about using a transaction and MAX() function to get last id inserted in the pupil table, but I'm not sure about it.

tirenweb
  • 30,963
  • 73
  • 183
  • 303

1 Answers1

0

It's a mistake to have $db->lastInsertId() within the foreach loop, as it may return unpredictable values. Please consider the code below:

try {
   $sql = "INSERT INTO pupil 
        (name, surname) 
        VALUES
        ('test_name', 'test_surname')";

   $st = $db->prepare($sql);
   $st->execute();

   // save pupil id into variable here:
   $pupil_id = $db->lastInsertId();

   // also there's no need to prepare the statement on each iteration:
   $sql = "INSERT INTO pupil_evening_activity 
            (pupil_id, evening_activity_id) 
            VALUES 
            (?, ?)";
   $st = $db->prepare($sql);

   foreach ($eveningActivityIds as $eveningActivityId) { 
      $st->execute(array($pupil_id, $eveningActivityId));
   }
} catch (PDOException $e) {
   echo $e->getMessage();
   return false;
}

Optimization: you can insert all acivities in one statement. It saves you few requests

try {
  $sql = "INSERT INTO pupil 
    (name, surname) 
    VALUES
    ('test_name', 'test_surname')";

  $st = $db->prepare($sql);
  $st->execute();

// save pupil id into variable here:
  $pupil_id = $db->lastInsertId();

  $sql = 'INSERT INTO pupil_evening_activity 
        (pupil_id, evening_activity_id) 
        VALUES '; 

  $values = array();

  foreach ($eveningActivityIds as $eveningActivityId) {
    $sql .= '(?, ?),';
    array_push($values, $pupil_id, $eveningActivityId);
  }

  $st = $db->prepare(chop($sql, ','));
  $st->execute($values);
} catch (PDOException $e) {
  echo $e->getMessage();
  return false;
}
Kosh
  • 16,966
  • 2
  • 19
  • 34
  • thanks! very useful!! but.. any idea about my main question? that is: is it correct using `PDO::lastInsertId()` to get the last `id` inserted in `pupil` table? – tirenweb May 29 '17 at 05:41
  • I believe it's fine unless you're building a highloaded system with thousands requests per second J. – Kosh May 29 '17 at 05:49