Mysql
 sql >> Base de données >  >> RDS >> Mysql

MySQLi :Insertion de plusieurs lignes avec une instruction préparée

Il est possible de préparer une requête d'instruction d'insertion en bloc en la construisant à la volée, mais cela nécessite quelques astuces. Les bits les plus importants utilisent str_pad() pour construire une chaîne de requête de longueur variable, et en utilisant call_user_func_array() pour appeler bind_param() avec un nombre variable de paramètres.

function insertBulkPrepared($db, $table, $fields, $types, $values) {
    $chunklength = 500;
    $fieldcount = count($fields);
    $fieldnames = '`'.join('`, `', $fields).'`';
    $prefix = "INSERT INTO `$table` ($fieldnames) VALUES ";
    $params = '(' . str_pad('', 3*$fieldcount - 2, '?, ') . '), ';
    $inserted = 0;

    foreach (array_chunk($values, $fieldcount*$chunklength) as $group) {
        $length = count($group);
        if ($inserted != $length) {
            if ($inserted) $stmt->close();
            $records = $length / $fieldcount;
            $query = $prefix . str_pad('', 3*$length + 2*($records - 1), $params);
            #echo "\n<br>Preparing '" . $query . "'";
            $stmt = $db->prepare($query);
            if (!$stmt) return false;
            $binding = str_pad('', $length, $types);
            $inserted = $length;
        }

        array_unshift($group, $binding);
        #echo "\n<br>Binding " . var_export($group, true);
        $bound = call_user_func_array(array($stmt, 'bind_param'), $group);
        if (!$bound) return false;
        if (!$stmt->execute()) return false;
    }

    if ($inserted) $stmt->close();
    return true;
}

Cette fonction prend votre $db en tant que mysqli instance, un nom de table, un tableau de noms de champs et un tableau plat de références à des valeurs. Il insère jusqu'à 500 enregistrements par requête, en réutilisant les instructions préparées lorsque cela est possible. Il renvoie true si toutes les insertions ont réussi, ou false si l'un d'entre eux a échoué. Mises en garde :

  • Les noms de table et de champ ne sont pas échappés ; Je vous laisse le soin de vous assurer qu'ils ne contiennent pas de backticks. Heureusement, ils ne doivent jamais provenir de l'entrée de l'utilisateur.
  • Si la longueur de $values n'est pas un multiple pair de la longueur de $fields , le morceau final échouera probablement au stade de la préparation.
  • De même, la longueur des $types le paramètre doit correspondre à la longueur de $fields dans la plupart des cas, en particulier lorsque certains d'entre eux diffèrent.
  • Il ne fait pas de distinction entre les trois façons d'échouer. Il ne garde pas non plus une trace du nombre d'insertions réussies, ni ne tente de continuer après une erreur.

Avec cette fonction définie, votre exemple de code peut être remplacé par quelque chose comme :

$inserts = array();
for ($j = 0; $j < $abilitiesMax - 2; $j++) {
    $inserts[] = &$abilityArray[$i]['match_id'];
    $inserts[] = &$abilityArray[$i]['player_slot'];
    $inserts[] = &$abilityArray[$i][$j]['ability'];
    $inserts[] = &$abilityArray[$i][$j]['time'];
    $inserts[] = &$abilityArray[$i][$j]['level'];
}

$fields = array('match_id', 'player_slot', 'ability', 'time', 'level');
$result = insertBulkPrepared($db, 'abilities', $fields, 'iiiii', $inserts);
if (!$result) {
    echo "<p>$db->error</p>";
    echo "<p>ERROR: when trying to insert abilities query</p>";
}

Ces esperluettes sont importantes, car mysqli_stmt::bind_param attend des références, qui ne sont pas fournies par call_user_func_array dans les versions récentes de PHP.

Vous ne nous avez pas donné l'instruction préparée d'origine, vous devez donc probablement ajuster les noms de table et de champ. Il semble également que votre code se trouve dans une boucle sur $i; dans ce cas, seul le for boucle doit être à l'intérieur de la boucle externe. Si vous prenez les autres lignes en dehors de la boucle, vous utiliserez un peu plus de mémoire pour construire le $inserts tableau, en échange d'insertions en bloc beaucoup plus efficaces.

Il est également possible de réécrire insertBulkPrepared() pour accepter un tableau multidimensionnel, éliminant une source d'erreur potentielle, mais cela nécessite d'aplatir le tableau après l'avoir fragmenté.