Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

MySQLi:Inserindo várias linhas com uma instrução preparada


É 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.