È possibile preparare una query di istruzioni di inserimento in blocco costruendola al volo, ma sono necessari alcuni trucchi. I bit più importanti stanno usando str_pad()
per costruire una stringa di query di lunghezza variabile e utilizzando call_user_func_array()
per chiamare bind_param()
con un numero variabile di parametri.
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;
}
Questa funzione prende il tuo $db
come mysqli
istanza, un nome di tabella, una matrice di nomi di campo e una matrice piatta di riferimenti a valori. Inserisce fino a 500 record per query, riutilizzando le istruzioni preparate quando possibile. Restituisce true
se tutti gli inserimenti sono riusciti, o false
se qualcuno di loro ha fallito. Avvertenze:
- I nomi delle tabelle e dei campi non vengono sottoposti a escape; Lascio a te assicurarti che non contengano backtick. Fortunatamente, non dovrebbero mai provenire dall'input dell'utente.
- Se la lunghezza di
$values
non è un multiplo pari della lunghezza di$fields
, il blocco finale probabilmente non riuscirà nella fase di preparazione. - Allo stesso modo, la lunghezza dei
$types
il parametro deve corrispondere alla lunghezza di$fields
nella maggior parte dei casi, in particolare quando alcuni di essi differiscono. - Non fa distinzione tra i tre modi per fallire. Inoltre, non tiene traccia di quanti inserimenti sono riusciti, né tenta di continuare dopo un errore.
Con questa funzione definita, il tuo codice di esempio può essere sostituito con qualcosa del tipo:
$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>";
}
Queste e commerciali sono importanti, perché mysqli_stmt::bind_param
si aspetta riferimenti, che non sono forniti da call_user_func_array
nelle versioni recenti di PHP.
Non ci hai fornito la dichiarazione originale preparata, quindi probabilmente devi modificare i nomi di tabella e campo. Sembra anche che il tuo codice si trovi all'interno di un ciclo su $i
; in tal caso, solo il for
il ciclo deve essere all'interno del ciclo esterno. Se porti le altre righe al di fuori del ciclo, utilizzerai un po' più di memoria per costruire $inserts
array, in cambio di inserimenti di massa molto più efficienti.
È anche possibile riscrivere insertBulkPrepared()
per accettare un array multidimensionale, eliminando una fonte di potenziale errore, ma ciò richiede l'appiattimento dell'array dopo averlo spezzettato.