Det är möjligt att förbereda en bulk insert-satsförfrågan genom att konstruera den direkt, men det tar några knep. De viktigaste bitarna använder str_pad()
för att konstruera en frågesträng med variabel längd och använda call_user_func_array()
för att anropa bind_param()
med ett variabelt antal parametrar.
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;
}
Den här funktionen tar din $db
som en mysqli
instans, ett tabellnamn, en array av fältnamn och en platt array av referenser till värden. Den infogar upp till 500 poster per fråga och återanvänder förberedda uttalanden när det är möjligt. Den returnerar true
om alla insättningar lyckades, eller false
om någon av dem misslyckades. Varningar:
- Tabell- och fältnamnen är inte kodade; Jag låter det vara upp till dig att se till att de inte innehåller backticks. Lyckligtvis bör de aldrig komma från användarinput.
- Om längden på
$values
är inte en jämn multipel av längden på$fields
, kommer den sista biten förmodligen att misslyckas vid förberedelsestadiet. - På samma sätt, längden på
$types
parametern ska matcha längden på$fields
i de flesta fall, särskilt när några av dem skiljer sig åt. - Den skiljer inte mellan de tre sätten att misslyckas. Den håller inte heller reda på hur många insättningar som lyckades, och försöker inte fortsätta efter ett fel.
Med den här funktionen definierad kan din exempelkod ersättas med något i stil med:
$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>";
}
Dessa et-tecken är viktiga eftersom mysqli_stmt::bind_param
förväntar sig referenser, som inte tillhandahålls av call_user_func_array
i de senaste versionerna av PHP.
Du gav oss inte det ursprungliga förberedda uttalandet, så du måste förmodligen justera tabell- och fältnamnen. Det ser också ut som att din kod sitter i en slinga över $i
; i så fall endast for
öglan måste vara inuti den yttre öglan. Om du tar de andra raderna utanför loopen kommer du att använda lite mer minne för att konstruera $inserts
array, i utbyte mot mycket effektivare bulkskär.
Det är också möjligt att skriva om insertBulkPrepared()
att acceptera en flerdimensionell array, vilket eliminerar en källa till potentiella fel, men det kräver att arrayen plattas till efter att den har formats i bitar.