insira várias linhas através de uma matriz php no mysql


129

Estou passando um grande conjunto de dados para uma tabela MySQL via PHP usando comandos insert e estou imaginando se é possível inserir aproximadamente 1000 linhas por vez através de uma consulta que não seja a adição de cada valor no final de uma string de uma milha e, em seguida, executando. Estou usando a estrutura CodeIgniter para que suas funções também estejam disponíveis para mim.


Dei uma resposta de acordo com sua pergunta para a inserção de várias linhas do Codeigniter.
Somnath Muluk

@SomnathMuluk Obrigado, no entanto, já faz um tempo desde que eu precisava responder a essa pergunta
:)

Eu recomendaria o uso da função insert_batch do CodeIgniter. Se você usa uma biblioteca, sempre tente aproveitar seus pontos fortes e os padrões de codificação.
Dewald Els

Acredito inserção lote é a melhor maneira de fazer isso consulte o link stackoverflow.com/questions/27206178/codeigniter-insert-batch
Syed Amir Bukhari

Respostas:


234

Montar uma INSERTinstrução com várias linhas é muito mais rápida no MySQL do que uma INSERTinstrução por linha.

Dito isso, parece que você pode estar enfrentando problemas de manipulação de string no PHP, o que é realmente um problema de algoritmo, não de linguagem. Basicamente, ao trabalhar com cadeias grandes, você deseja minimizar as cópias desnecessárias. Principalmente, isso significa que você deseja evitar a concatenação. A maneira mais rápida e eficiente de criar uma string grande, como inserir centenas de linhas em uma, é aproveitar a implode()função e a atribuição da matriz.

$sql = array(); 
foreach( $data as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $sql));

A vantagem dessa abordagem é que você não copia e copia novamente a instrução SQL que você montou até agora com cada concatenação; em vez disso, o PHP faz isso uma vez na implode()declaração. Esta é uma grande vitória.

Se você tiver muitas colunas para montar e uma ou mais forem muito longas, também poderá criar um loop interno para fazer a mesma coisa e usar implode()para atribuir a cláusula values ​​à matriz externa.


5
Obrigado por isso! Entre a falta de um colchete no final da função, se alguém estiver planejando copiá-la. mysql_real_query ('INSERT INTO VALORES DA tabela (texto, categoria)' .implode (','. $ sql));
toofarsideways

3
Obrigado! Fixo. (Muitas vezes eu fazer isso ...)
staticsan

1
ea consulta deve realmente ser 'tabela INSERT INTO (texto, categoria) VALUES' .implode 'suspirar de codificação 4am leva a terrível depuração :( (',' $ sql.)
toofarsideways

3
Acredito que esse código criará uma solução para o meu projeto mais recente. Minha pergunta aqui é: isso é seguro contra injeção de SQL? Meus planos são mudar mysql_real_escape_stringcom mysqli_real_escape_stringe mysql_querycom o mysqli_queryuso do MySQLi e estes foram preteridos a partir do PHP5. Muito Obrigado!
precisa saber é

2
mysql_*foi removido do PHP, portanto, certifique-se de usar a mysqli_*interface.
Rick James

60

Inserção múltipla / inserção em lote agora é suportada pelo codeigniter. Eu tive o mesmo problema. Embora seja muito tarde para responder à pergunta, isso ajudará alguém. Por isso, respondendo a essa pergunta.

$data = array(
   array(
      'title' => 'My title' ,
      'name' => 'My Name' ,
      'date' => 'My date'
   ),
   array(
      'title' => 'Another title' ,
      'name' => 'Another Name' ,
      'date' => 'Another date'
   )
);

$this->db->insert_batch('mytable', $data);

// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'), ('Another title', 'Another name', 'Another date')

2
Eu acho que esta é a maneira mais recomendada de fazer inserção de várias linhas, usando mysql_query. Porque, quando usamos uma estrutura, é uma boa prática sempre usar os recursos internos da estrutura.
Praneeth Nidarshan

22

Você pode preparar a consulta para inserir uma linha usando a classe mysqli_stmt e, em seguida, iterar sobre a matriz de dados. Algo como:

$stmt =  $db->stmt_init();
$stmt->prepare("INSERT INTO mytbl (fld1, fld2, fld3, fld4) VALUES(?, ?, ?, ?)");
foreach($myarray as $row)
{
    $stmt->bind_param('idsb', $row['fld1'], $row['fld2'], $row['fld3'], $row['fld4']);
    $stmt->execute();
}
$stmt->close();

Onde 'idsb' são os tipos de dados que você está vinculando (int, double, string, blob).


6
Recentemente, executei alguns benchmarks comparando inserção em massa e instruções de inserção preparadas, conforme mencionado aqui. Para cerca de 500 pastilhas, o método de pastilhas preparadas foi concluído entre 2,6-4,4 segundos e o método de pastilhas em massa em 0,12-0,35 segundos. Eu pensaria que o mysql "agruparia" essas instruções preparadas e executaria tão bem quanto as inserções em massa, mas em uma configuração padrão, a diferença de desempenho é enorme, aparentemente. (Consultas Btw todos referenciados estavam correndo dentro de uma única transação para cada teste, como para evitar auto-cometer)
Motin

16

Sei que essa é uma consulta antiga, mas estava lendo e pensei em adicionar o que encontrei em outro lugar:

O mysqli no PHP 5 é um objeto com algumas boas funções que permitem acelerar o tempo de inserção da resposta acima:

$mysqli->autocommit(FALSE);
$mysqli->multi_query($sqlCombined);
$mysqli->autocommit(TRUE);

Desativar a confirmação automática ao inserir muitas linhas acelera bastante a inserção, portanto, desative-a e execute como mencionado acima, ou apenas crie uma string (sqlCombined) que é muitas instruções de inserção separadas por ponto-e-vírgula e multi-consultas.

Espero que isso ajude alguém a economizar tempo (pesquisando e inserindo!)

R


Este é o erro que recebi usando sua ideia: "Erro fatal: chame uma função de membro autocommit () em null em /homepages/25/d402746174/htdocs/MoneyMachine/saveQuotes.php na linha 30"
user3217883

8

Você sempre pode usar o mysql LOAD DATA:

LOAD DATA LOCAL INFILE '/full/path/to/file/foo.csv' INTO TABLE `footable` FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n' 

para fazer inserções em massa em vez de usar várias INSERTinstruções.


Eu tinha analisado isso, mas preciso manipular os dados antes de inseri-los. Foi-me dado como um produto cartesiano de um conjunto de 1400 por 1400 de valores int muitos dos quais zero. Preciso para convertê-lo em um de muitos para muitos relação usando uma tabela intermediário para economizar espaço, portanto, a necessidade de inserções em oposição a uma inserção em massa
toofarsideways

Você sempre pode gerar um arquivo CSV depois manipulá-lo e chamar a declaração mysql que carrega os dados
Alexander Jardim

Eu acho que é útil saber que o caminho é local para o seu cliente SQL, e não no servidor SQL. O arquivo é carregado no servidor e lido por ele. Eu pensei que o arquivo já deveria estar no servidor, o que não é o caso. Se já estiver no servidor, remova o LOCALbit.
Kyle

5

Bem, você não deseja executar 1000 chamadas de consulta, mas fazer isso é bom:

$stmt= array( 'array of statements' );
$query= 'INSERT INTO yourtable (col1,col2,col3) VALUES ';
foreach( $stmt AS $k => $v ) {
  $query.= '(' .$v. ')'; // NOTE: you'll have to change to suit
  if ( $k !== sizeof($stmt)-1 ) $query.= ', ';
}
$r= mysql_query($query);

Dependendo da fonte de dados, preencher a matriz pode ser tão fácil quanto abrir um arquivo e despejar o conteúdo em uma matriz via file().


1
É mais limpo se você mover isso se acima da consulta e alterá-lo para algo como se ($ k> 0).
precisa saber é

@cherouvim ... Bem, você está certo sobre isso. Obrigado pela sua contribuição. Ao reler o exemplo que forneci, não estou conseguindo entender seu ponto de vista. Cuidado para elaborar (via pastebin, etc?). Thanks-
BDL

3
$query= array(); 
foreach( $your_data as $row ) {
    $query[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $query));

1

Você pode fazer isso de várias maneiras no codeigniter, por exemplo

Primeiro por loop

foreach($myarray as $row)
{
   $data = array("first"=>$row->first,"second"=>$row->sec);
   $this->db->insert('table_name',$data);
}

Segundo - Por lote de inserção

$data = array(
       array(
          'first' => $myarray[0]['first'] ,
          'second' => $myarray[0]['sec'],
        ),
       array(
          'first' => $myarray[1]['first'] ,
          'second' => $myarray[1]['sec'],
        ),
    );

    $this->db->insert_batch('table_name', $data);

Terceira via - Por passagem de múltiplos valores

$sql = array(); 
foreach( $myarray as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['first']).'", '.$row['sec'].')';
}
mysql_query('INSERT INTO table (first, second) VALUES '.implode(',', $sql));

1

Embora seja tarde demais para responder a esta pergunta. Aqui está a minha resposta sobre o mesmo.

Se você estiver usando o CodeIgniter, poderá usar os métodos embutidos definidos na classe query_builder.

$ this-> db-> insert_batch ()

Gera uma string de inserção com base nos dados que você fornece e executa a consulta. Você pode passar uma matriz ou um objeto para a função. Aqui está um exemplo usando uma matriz:

$data = array(
    array(
            'title' => 'My title',
            'name' => 'My Name',
            'date' => 'My date'
    ),
    array(
            'title' => 'Another title',
            'name' => 'Another Name',
            'date' => 'Another date'
    )

);

$this->db->insert_batch('mytable', $data);
// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'),  ('Another title', 'Another name', 'Another date')

O primeiro parâmetro conterá o nome da tabela, o segundo é uma matriz associativa de valores.

Você pode encontrar mais detalhes sobre o query_builder aqui


0

Eu criei uma classe que executa várias linhas que é usada da seguinte maneira:

$pdo->beginTransaction();
$pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
$pmi->insertRow($data);
// ....
$pmi->insertRow($data);
$pmi->purgeRemainingInserts();
$pdo->commit();

onde a classe é definida da seguinte maneira:

class PDOMultiLineInserter {
    private $_purgeAtCount;
    private $_bigInsertQuery, $_singleInsertQuery;
    private $_currentlyInsertingRows  = array();
    private $_currentlyInsertingCount = 0;
    private $_numberOfFields;
    private $_error;
    private $_insertCount = 0;

    /**
     * Create a PDOMultiLine Insert object.
     *
     * @param PDO $pdo              The PDO connection
     * @param type $tableName       The table name
     * @param type $fieldsAsArray   An array of the fields being inserted
     * @param type $bigInsertCount  How many rows to collect before performing an insert.
     */
    function __construct(PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
        $this->_numberOfFields = count($fieldsAsArray);
        $insertIntoPortion = "REPLACE INTO `$tableName` (`".implode("`,`", $fieldsAsArray)."`) VALUES";
        $questionMarks  = " (?".str_repeat(",?", $this->_numberOfFields - 1).")";

        $this->_purgeAtCount = $bigInsertCount;
        $this->_bigInsertQuery    = $pdo->prepare($insertIntoPortion.$questionMarks.str_repeat(", ".$questionMarks, $bigInsertCount - 1));
        $this->_singleInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks);
    }

    function insertRow($rowData) {
        // @todo Compare speed
        // $this->_currentlyInsertingRows = array_merge($this->_currentlyInsertingRows, $rowData);
        foreach($rowData as $v) array_push($this->_currentlyInsertingRows, $v);
        //
        if (++$this->_currentlyInsertingCount == $this->_purgeAtCount) {
            if ($this->_bigInsertQuery->execute($this->_currentlyInsertingRows) === FALSE) {
                $this->_error = "Failed to perform a multi-insert (after {$this->_insertCount} inserts), the following errors occurred:".implode('<br/>', $this->_bigInsertQuery->errorInfo());
                return false;
            }
            $this->_insertCount++;

            $this->_currentlyInsertingCount = 0;
            $this->_currentlyInsertingRows = array();
        }
        return true;
    }

    function purgeRemainingInserts() {
        while ($this->_currentlyInsertingCount > 0) {
            $singleInsertData = array();
            // @todo Compare speed - http://www.evardsson.com/blog/2010/02/05/comparing-php-array_shift-to-array_pop/
            // for ($i = 0; $i < $this->_numberOfFields; $i++) $singleInsertData[] = array_pop($this->_currentlyInsertingRows); array_reverse($singleInsertData);
            for ($i = 0; $i < $this->_numberOfFields; $i++)     array_unshift($singleInsertData, array_pop($this->_currentlyInsertingRows));

            if ($this->_singleInsertQuery->execute($singleInsertData) === FALSE) {
                $this->_error = "Failed to perform a small-insert (whilst purging the remaining rows; the following errors occurred:".implode('<br/>', $this->_singleInsertQuery->errorInfo());
                return false;
            }
            $this->_currentlyInsertingCount--;
        }
    }

    public function getError() {
        return $this->_error;
    }
}

0

Use inserir lote no codeigniter para inserir várias linhas de dados.

$this->db->insert_batch('tabname',$data_array); // $data_array holds the value to be inserted

0

Eu criei essa função simples que vocês podem usar facilmente. Você precisará passar o nome da ($tbl)tabela, o campo da tabela para os ($insertFieldsArr)dados inseridos, matriz de dados ($arr).

insert_batch('table',array('field1','field2'),$dataArray);

    function insert_batch($tbl,$insertFieldsArr,$arr){ $sql = array(); 
    foreach( $arr as $row ) {
        $strVals='';
        $cnt=0;
        foreach($insertFieldsArr as $key=>$val){
            if(is_array($row)){
                $strVals.="'".mysql_real_escape_string($row[$cnt]).'\',';
            }
            else{
                $strVals.="'".mysql_real_escape_string($row).'\',';
            }
            $cnt++;
        }
        $strVals=rtrim($strVals,',');
        $sql[] = '('.$strVals.')';
    }

    $fields=implode(',',$insertFieldsArr);
    mysql_query('INSERT INTO `'.$tbl.'` ('.$fields.') VALUES '.implode(',', $sql));
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.