É possível preparar uma consulta de instrução de inserção em massa construindo-a rapidamente, mas são necessários alguns truques. Os bits mais importantes estão usando
str_pad()
para construir uma string de consulta de comprimento variável e usando call_user_func_array()
para chamar bind_param()
com um número variável de parâmetros. 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;
}
Esta função pega seu
$db
como um mysqli
instância, um nome de tabela, uma matriz de nomes de campo e uma matriz simples de referências a valores. Ele insere até 500 registros por consulta, reutilizando instruções preparadas quando possível. Ele retorna true
se todas as inserções foram bem-sucedidas ou false
se algum deles falhou. Ressalvas:- Os nomes de tabela e campo não são escapados; Deixo para você garantir que eles não contenham backticks. Felizmente, eles nunca devem vir da entrada do usuário.
- Se o comprimento de
$values
não é um múltiplo par do comprimento de$fields
, a parte final provavelmente falhará no estágio de preparação. - Da mesma forma, o comprimento dos
$types
parâmetro deve corresponder ao comprimento de$fields
na maioria dos casos, principalmente quando alguns deles diferem. - Não faz distinção entre as três maneiras de falhar. Ele também não registra quantas inserções foram bem-sucedidas nem tenta continuar após um erro.
Com esta função definida, seu código de exemplo pode ser substituído por algo como:
$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>";
}
Esses ampersands são importantes, porque
mysqli_stmt::bind_param
espera referências, que não são fornecidas por call_user_func_array
em versões recentes do PHP. Você não nos deu a declaração preparada original, então você provavelmente precisa ajustar os nomes da tabela e dos campos. Também parece que seu código fica dentro de um loop sobre
$i
; nesse caso, apenas o for
loop precisa estar dentro do loop externo. Se você pegar as outras linhas fora do loop, você usará um pouco mais de memória construindo o $inserts
array, em troca de insertos a granel muito mais eficientes. Também é possível reescrever
insertBulkPrepared()
para aceitar um array multidimensional, eliminando uma fonte de erro potencial, mas isso requer achatar o array depois de fragmentá-lo.