Pergunta

Eu sei que você pode inserir várias linhas de uma vez. Existe uma maneira de atualizar várias linhas de uma vez (como em uma consulta) no MySQL?

Editar:Por exemplo eu tenho o seguinte

Name   id  Col1  Col2
Row1   1    6     1
Row2   2    2     3
Row3   3    9     5
Row4   4    16    8

Quero combinar todas as atualizações a seguir em uma consulta

UPDATE table SET Col1 = 1 WHERE id = 1;
UPDATE table SET Col1 = 2 WHERE id = 2;
UPDATE table SET Col2 = 3 WHERE id = 3;
UPDATE table SET Col1 = 10 WHERE id = 4;
UPDATE table SET Col2 = 12 WHERE id = 4;
Foi útil?

Solução

Sim, isso é possível - você pode usar INSERT ...NA ATUALIZAÇÃO DE CHAVE DUPLICADA.

Usando seu exemplo:

INSERT INTO table (id,Col1,Col2) VALUES (1,1,1),(2,2,3),(3,9,3),(4,10,12)
ON DUPLICATE KEY UPDATE Col1=VALUES(Col1),Col2=VALUES(Col2);

Outras dicas

Como você possui valores dinâmicos, é necessário usar um IF ou CASE para que as colunas sejam atualizadas.Fica meio feio, mas deve funcionar.

Usando seu exemplo, você poderia fazer assim:

UPDATE table SET Col1 = CASE id 
                          WHEN 1 THEN 1 
                          WHEN 2 THEN 2 
                          WHEN 4 THEN 10 
                          ELSE Col1 
                        END, 
                 Col2 = CASE id 
                          WHEN 3 THEN 3 
                          WHEN 4 THEN 12 
                          ELSE Col2 
                        END
             WHERE id IN (1, 2, 3, 4);

A pergunta é antiga, mas gostaria de estender o tópico com outra resposta.

O que quero dizer é que a maneira mais fácil de conseguir isso é agrupar várias consultas em uma transação.A resposta aceita INSERT ... ON DUPLICATE KEY UPDATE é um bom hack, mas deve-se estar ciente de suas desvantagens e limitações:

  • Como foi dito, se acontecer de você iniciar a consulta com linhas cujas chaves primárias não existem na tabela, a consulta insere novos registros "incompletos".Provavelmente não é o que você quer
  • Se você tiver uma tabela com um campo não nulo sem valor padrão e não quiser alterar esse campo na consulta, você obterá "Field 'fieldname' doesn't have a default value" Aviso do MySQL mesmo se você não inserir uma única linha.Você terá problemas se decidir ser rigoroso e transformar os avisos do MySQL em exceções de tempo de execução em seu aplicativo.

Fiz alguns testes de desempenho para três das variantes sugeridas, incluindo o INSERT ... ON DUPLICATE KEY UPDATE variante, uma variante com cláusula "case/when/then" e uma abordagem ingênua com transação.Você pode obter o código python e os resultados aqui.A conclusão geral é que a variante com instrução case acaba sendo duas vezes mais rápida que duas outras variantes, mas é muito difícil escrever código correto e seguro para injeção para ela, então, pessoalmente, mantenho a abordagem mais simples:usando transações.

Editar: Descobertas de Dakusan provar que minhas estimativas de desempenho não são totalmente válidas.Por favor, veja esta resposta para outra pesquisa mais elaborada.

Não sei por que outra opção útil ainda não foi mencionada:

UPDATE my_table m
JOIN (
    SELECT 1 as id, 10 as _col1, 20 as _col2
    UNION ALL
    SELECT 2, 5, 10
    UNION ALL
    SELECT 3, 15, 30
) vals ON m.id = vals.id
SET col1 = _col1, col2 = _col2;

Todos os itens a seguir se aplicam ao InnoDB.

Sinto que é importante conhecer as velocidades dos 3 métodos diferentes.

Existem 3 métodos:

  1. INSERIR:INSERIR com ATUALIZAÇÃO DE CHAVE DUPLICADA
  2. TRANSAÇÃO:Onde você faz uma atualização para cada registro em uma transação
  3. CASO:Em que você um caso/quando para cada registro diferente dentro de um UPDATE

Acabei de testar isso e o método INSERT foi 6,7x mais rápido para mim do que o método TRANSACTION.Tentei um conjunto de 3.000 e 30.000 linhas.

O método TRANSACTION ainda precisa executar cada consulta individualmente, o que leva tempo, embora agrupe os resultados na memória, ou algo assim, durante a execução.O método TRANSACTION também é bastante caro tanto em logs de replicação quanto de consulta.

Pior ainda, o método CASE foi 41,1x mais lento que o método INSERT com 30.000 registros (6,1x mais lento que TRANSACTION).E 75x mais lento no MyISAM.Os métodos INSERT e CASE atingiram o equilíbrio em aproximadamente 1.000 registros.Mesmo com 100 registros, o método CASE é MUITO mais rápido.

Então, em geral, acho que o método INSERT é melhor e mais fácil de usar.As consultas são menores e mais fáceis de ler e ocupam apenas 1 consulta de ação.Isso se aplica tanto ao InnoDB quanto ao MyISAM.

Coisas bônus:

A solução para o problema do campo não padrão INSERT é desligar temporariamente os modos SQL relevantes: SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TA‌​BLES",""),"STRICT_AL‌​L_TABLES","").Certifique-se de salvar o sql_mode primeiro se você planeja revertê-lo.

Quanto a outros comentários que vi que dizem que o auto_increment aumenta usando o método INSERT, testei também e parece que não é o caso.

O código para executar os testes é o seguinte.Ele também gera arquivos .SQL para remover a sobrecarga do interpretador php

<?
//Variables
$NumRows=30000;

//These 2 functions need to be filled in
function InitSQL()
{

}
function RunSQLQuery($Q)
{

}

//Run the 3 tests
InitSQL();
for($i=0;$i<3;$i++)
    RunTest($i, $NumRows);

function RunTest($TestNum, $NumRows)
{
    $TheQueries=Array();
    $DoQuery=function($Query) use (&$TheQueries)
    {
        RunSQLQuery($Query);
        $TheQueries[]=$Query;
    };

    $TableName='Test';
    $DoQuery('DROP TABLE IF EXISTS '.$TableName);
    $DoQuery('CREATE TABLE '.$TableName.' (i1 int NOT NULL AUTO_INCREMENT, i2 int NOT NULL, primary key (i1)) ENGINE=InnoDB');
    $DoQuery('INSERT INTO '.$TableName.' (i2) VALUES ('.implode('), (', range(2, $NumRows+1)).')');

    if($TestNum==0)
    {
        $TestName='Transaction';
        $Start=microtime(true);
        $DoQuery('START TRANSACTION');
        for($i=1;$i<=$NumRows;$i++)
            $DoQuery('UPDATE '.$TableName.' SET i2='.(($i+5)*1000).' WHERE i1='.$i);
        $DoQuery('COMMIT');
    }

    if($TestNum==1)
    {
        $TestName='Insert';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf("(%d,%d)", $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery('INSERT INTO '.$TableName.' VALUES '.implode(', ', $Query).' ON DUPLICATE KEY UPDATE i2=VALUES(i2)');
    }

    if($TestNum==2)
    {
        $TestName='Case';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf('WHEN %d THEN %d', $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery("UPDATE $TableName SET i2=CASE i1\n".implode("\n", $Query)."\nEND\nWHERE i1 IN (".implode(',', range(1, $NumRows)).')');
    }

    print "$TestName: ".(microtime(true)-$Start)."<br>\n";

    file_put_contents("./$TestName.sql", implode(";\n", $TheQueries).';');
}
UPDATE table1, table2 SET table1.col1='value', table2.col1='value' WHERE table1.col3='567' AND table2.col6='567'

Isso deve funcionar para você.

Há uma referência em o manual do MySQL para várias tabelas.

Use uma tabela temporária

// Reorder items
function update_items_tempdb(&$items)
{
    shuffle($items);
    $table_name = uniqid('tmp_test_');
    $sql = "CREATE TEMPORARY TABLE `$table_name` ("
        ."  `id` int(10) unsigned NOT NULL AUTO_INCREMENT"
        .", `position` int(10) unsigned NOT NULL"
        .", PRIMARY KEY (`id`)"
        .") ENGINE = MEMORY";
    query($sql);
    $i = 0;
    $sql = '';
    foreach ($items as &$item)
    {
        $item->position = $i++;
        $sql .= ($sql ? ', ' : '')."({$item->id}, {$item->position})";
    }
    if ($sql)
    {
        query("INSERT INTO `$table_name` (id, position) VALUES $sql");
        $sql = "UPDATE `test`, `$table_name` SET `test`.position = `$table_name`.position"
            ." WHERE `$table_name`.id = `test`.id";
        query($sql);
    }
    query("DROP TABLE `$table_name`");
}

Você pode usar o alias da mesma tabela para fornecer os IDs que deseja inserir (se estiver fazendo uma atualização linha por linha:

UPDATE table1 tab1, table1 tab2 -- alias references the same table
SET 
col1 = 1
,col2 = 2
. . . 
WHERE 
tab1.id = tab2.id;

Além disso, deve parecer óbvio que você também pode atualizar de outras tabelas.Nesse caso, a atualização funciona como uma instrução “SELECT”, fornecendo os dados da tabela que você está especificando.Você está declarando explicitamente em sua consulta os valores de atualização para que a segunda tabela não seja afetada.

Por que ninguém menciona múltiplas instruções em uma consulta?

No php você usa multi_query método da instância mysqli.

De manual de php

O MySQL opcionalmente permite ter múltiplas instruções em uma string de instruções.O envio de várias instruções de uma só vez reduz as viagens de ida e volta cliente-servidor, mas requer tratamento especial.

Aqui está o resultado comparando com outros 3 métodos na atualização 30.000 raw.O código pode ser encontrado aqui que é baseado na resposta de @Dakusan

Transação:5.5194580554962
Inserir:0,20669293403625
Caso:16.474853992462
Multi:0,0412278175354

Como você pode ver, a consulta com múltiplas instruções é mais eficiente do que a resposta mais alta.

Se você receber uma mensagem de erro como esta:

PHP Warning:  Error while sending SET_OPTION packet

Você pode precisar aumentar o max_allowed_packet no arquivo de configuração do mysql que na minha máquina é /etc/mysql/my.cnf e então reinicie o mysqld.

Você também pode estar interessado em usar junções em atualizações, o que também é possível.

Update someTable Set someValue = 4 From someTable s Inner Join anotherTable a on s.id = a.id Where a.id = 4
-- Only updates someValue in someTable who has a foreign key on anotherTable with a value of 4.

Editar:Se os valores que você está atualizando não vierem de algum outro lugar no banco de dados, você precisará emitir várias consultas de atualização.

Existe uma configuração que você pode alterar chamada 'multi instrução' que desativa o 'mecanismo de segurança' do MySQL implementado para evitar (mais de um) comando de injeção.Típico da implementação 'brilhante' do MySQL, também impede que o usuário faça consultas eficientes.

Aqui (http://dev.mysql.com/doc/refman/5.1/en/mysql-set-server-option.html) são algumas informações sobre a implementação C da configuração.

Se você estiver usando PHP, você pode usar o mysqli para fazer múltiplas instruções (acho que o php já vem com o mysqli há algum tempo)

$con = new mysqli('localhost','user1','password','my_database');
$query = "Update MyTable SET col1='some value' WHERE id=1 LIMIT 1;";
$query .= "UPDATE MyTable SET col1='other value' WHERE id=2 LIMIT 1;";
//etc
$con->multi_query($query);
$con->close();

Espero que ajude.

usar

REPLACE INTO`table` VALUES (`id`,`col1`,`col2`) VALUES
(1,6,1),(2,2,3),(3,9,5),(4,16,8);

Observe:

  • id deve ser uma chave primária exclusiva
  • Se você usar teclas estranhas para fazer referência à tabela, substitua as excluídas, e as inserções, para que isso possa causar um erro

O seguinte atualizará todas as linhas em uma tabela

Update Table Set
Column1 = 'New Value'

O próximo atualizará todas as linhas onde o valor da Coluna2 for maior que 5

Update Table Set
Column1 = 'New Value'
Where
Column2 > 5

Há tudo Tecnologia desconhecidaexemplo de atualização de mais de uma tabela

UPDATE table1, table2 SET
table1.col1 = 'value',
table2.col1 = 'value'
WHERE
table1.col3 = '567'
AND table2.col6='567'

Sim ..é possível usar a instrução sql INSERT ON DUPLICATE KEY UPDATE ..sintaxe:Inserir em tabela_name (a, b, c) valores (1,2,3), (4,5,6) na chave de chave duplicada a = valores (a), b = valores (b), c = valores (c)

Com PHP eu fiz isso.Use ponto e vírgula, divida-o em array e envie via loop.

$con = new mysqli('localhost','user1','password','my_database');
$batchUpdate = true; /*You can choose between batch and single query */
$queryIn_arr = explode(";", $queryIn);

if($batchUpdate)    /* My SQL prevents multiple insert*/
{
    foreach($queryIn_arr as $qr)
    {
        if(strlen($qr)>3)
        {
            //echo '<br>Sending data to SQL1:<br>'.$qr.'</br>';
            $result = $conn->query($qr);
        }

    }
}
else
{
    $result = $conn->query($queryIn);
}
$con->close();
UPDATE tableName SET col1='000' WHERE id='3' OR id='5'

Isso deve alcançar o que você está procurando.Basta adicionar mais IDs.Eu testei.

UPDATE `your_table` SET 

`something` = IF(`id`="1","new_value1",`something`), `smth2` = IF(`id`="1", "nv1",`smth2`),
`something` = IF(`id`="2","new_value2",`something`), `smth2` = IF(`id`="2", "nv2",`smth2`),
`something` = IF(`id`="4","new_value3",`something`), `smth2` = IF(`id`="4", "nv3",`smth2`),
`something` = IF(`id`="6","new_value4",`something`), `smth2` = IF(`id`="6", "nv4",`smth2`),
`something` = IF(`id`="3","new_value5",`something`), `smth2` = IF(`id`="3", "nv5",`smth2`),
`something` = IF(`id`="5","new_value6",`something`), `smth2` = IF(`id`="5", "nv6",`smth2`) 

// Você acabou de construir em php como

$q = 'UPDATE `your_table` SET ';

foreach($data as $dat){

  $q .= '

       `something` = IF(`id`="'.$dat->id.'","'.$dat->value.'",`something`), 
       `smth2` = IF(`id`="'.$dat->id.'", "'.$dat->value2.'",`smth2`),';

}

$q = substr($q,0,-1);

Então você pode atualizar a tabela de furos com uma consulta

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top