2008-08-06 16 views
305

Sé que puede insertar varias filas a la vez, ¿hay alguna manera de actualizar varias filas a la vez (como en una consulta) en MySQL?Múltiples actualizaciones en MySQL

Editar: Por ejemplo Tengo el siguiente

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

quiero combinar todos estos cambios y actualizaciones en una 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; 

Respuesta

531

Sí, eso es posible: puede usar INSERTAR ... EN ACTUALIZACIÓN DE LLAVE DUPLICADA.

Usando su ejemplo:

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); 
+16

Si no hay duplicados, entonces no quiero que se inserte esa fila. ¿Qué debería hacer? porque estoy obteniendo información de otro sitio que mantiene tablas con id. Estoy insertando valores con respecto a esa identificación. si el sitio tiene nuevos registros, terminaré insertando solo los identificadores y el recuento, excepto toda la demás información. si y solo si hay una entrada para el ID, entonces debería actualizarse, de lo contrario debería omitirse. ¿Qué debo hacer? –

+0

así que si es como insertar en salto nulo u omitir vacío en la actualización duplicada ... entonces eso sería bueno. –

+20

Nota: esta respuesta también asume que la ID es la clave principal – JM4

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

Esto debería funcionar para ti.

Hay una referencia en the MySQL manual para varias tablas.

-3

A continuación se actualizará todos los registros en una tabla

Update Table Set 
Column1 = 'New Value' 

el próximo será actualizar todas las filas donde el valor de la columna 2 es más de 5

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

No es ejemplo de todo Unkwntech 's de actualizar más de una tabla

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

Puede que también le interese usar joi ns en las actualizaciones, que también es posible.

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: Si los valores que está actualizando no vienen de otra parte de la base de datos, tendrá que realizar varias consultas de actualización.

-5
UPDATE tableName SET col1='000' WHERE id='3' OR id='5' 

Esto debería lograr lo que estás buscando. Solo agrega más identificadores. Lo he probado

104

Puesto que usted tiene valores dinámicos, es necesario utilizar un IF o CASE para las columnas que se actualizan. Se pone un poco feo, pero debería funcionar.

Usando su ejemplo, podría hacerlo como:

 
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); 
+0

tal vez no tan bonito para escribir para la actualización dinámica, pero un vistazo interesante a la funcionalidad de la cubierta ... –

+0

@ user2536953, también puede ser bueno para la actualización dinámica. Por ejemplo, utilicé esa solución en loop en php: '$ commandTxt = 'ACTUALIZAR operaciones SET chunk_finished = CASE id'; foreach ($ blockOperationChecked as $ operationID => $ operationChecked) $ commandTxt. = "CUANDO $ operationID THEN $ operationChecked"; $ commandTxt. = 'ELSE id END WHERE id IN (' .implode (',', array_keys (blockOperationChecked)). ');'; ' –

3

Hay una (más de uno) comando de ajuste se puede modificar llamado "multi declaración de que desactiva 'mecanismo de seguridad' de MySQL implementado para evitar la inyección . Típico de la implementación 'brillante' de MySQL, también evita que el usuario haga consultas eficientes.

Aquí (http://dev.mysql.com/doc/refman/5.1/en/mysql-set-server-option.html) es información sobre la implementación C de la configuración.

Si está usando PHP, puede utilizar mysqli hacer declaraciones múltiples (creo php ha enviado con mysqli desde hace un tiempo)

$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 ayude.

+3

Esto es lo mismo que enviar las consultas por separado. La única diferencia es que lo envía todo en un paquete de red, pero las ACTUALIZACIONES se seguirán procesando como consultas separadas. Mejor es envolverlos en una transacción, luego los cambios se comprometerán a la mesa de una vez. – Marki555

+2

¿Cómo envolverlos en una transacción? Muéstranos, por favor. – TomeeNS

+0

@TomeeNS Use 'mysqli :: begin_transaction (..)' antes de enviar la consulta y 'mysql :: commit (..)' después. O use 'START TRANSACTION' como primer y' COMMIT' como último enunciado en la consulta misma. –

7

Usar una tabla temporal

// 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`"); 
} 
2

Puede asignar un alias a la misma mesa para darle los identificadores que desea insertar por (si se está haciendo una actualización de fila por fila:

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

Además, debería parecer obvio que también puede actualizar desde otras tablas. En este caso, la actualización funciona como una declaración "SELECCIONAR", que le proporciona los datos de la tabla que está especificando. Está indicando explícitamente en su consulta los valores de actualización entonces, la segunda tabla no se ve afectada.

77

La pregunta es antigua, pero me gustaría extender el tema con otra respuesta.

Mi punto es que la forma más fácil de lograrlo es simplemente para envolver múltiples consultas con una transacción. La respuesta aceptada INSERT ... ON DUPLICATE KEY UPDATE es un buen truco, pero uno debe ser consciente de sus desventajas y limitaciones:

  • Como se dice, si le toca lanzar la consulta con filas cuyas claves primaria no existir en la tabla, la consulta inserta nuevos registros "medio horneados". Probablemente no es lo que quiere
  • Si tiene una tabla con un campo no nulo sin valor predeterminado y no desea tocar este campo en la consulta, obtendrá "Field 'fieldname' doesn't have a default value" advertencia de MySQL incluso si no inserta un fila única en absoluto. Te meterá en problemas, si decides ser estricto y convertir las advertencias de mysql en excepciones de tiempo de ejecución en tu aplicación.

hice algunas pruebas de rendimiento para tres de las variantes sugeridas, incluyendo la variante INSERT ... ON DUPLICATE KEY UPDATE, una variante con "cuando caso// then" cláusula y un enfoque ingenuo con la transacción. Puede obtener el código python y los resultados here. La conclusión general es que la variante con declaración de caso resulta ser dos veces más rápida que otras dos variantes, pero es bastante difícil escribir un código correcto y seguro para la inyección, así que me atengo al enfoque más simple: usar transacciones.

Editar: Los resultados de Dakusan demuestran que mis estimaciones de rendimiento no son del todo válidas. Por favor, consulte this answer para otra investigación más elaborada.

+0

Usando transacciones, consejo muy agradable (y simple)! – mTorres

+0

¿Qué sucede si mis tablas no son de tipo InnoDB? – TomeeNS

+1

¿Podría alguien proporcionar un enlace a las transacciones para hacer esto? ¿Y/o código para un código de inyección segura para la variante con declaración de caso? –

-7
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`) 

// Sólo la construcción de ésta en 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); 

para que pueda actualizar la tabla de agujeros con una consulta

+0

¿Por qué esta solución es tan mala? Estoy hablando del SQL y no del código PHP ... –

+0

No he votado negativamente, pero creo que la objeción es hacer el set, cuando no es necesario (y aún lo estás haciendo, cuando estás configurando 'something' a 'algo') – v010dya

45

No sé por qué otra opción útil sin embargo, no se menciona:

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; 
+4

Este es el mejor. Especialmente si está extrayendo los valores de actualización de otra consulta SQL como lo estaba haciendo. – v010dya

+1

Esto fue genial para una actualización en una tabla con una gran cantidad de columnas. Probablemente usaré esta consulta mucho en el futuro. ¡Gracias! –

+0

He intentado este tipo de consulta. Pero cuando los registros llegan a 30k el servidor Boundary se detiene. hay alguna otra solucion? –

-3

Sí .. es posible usar INSERTAR EN DUPLICADO ACTUALIZACIÓN DE LLAVE sentencia sql .. sy ntax: INSERT INTO table_name (a, b, c) VALUES (1,2,3), (4,5,6) ON DUPLICATE KEY UPDATE a = VALORES (a), b = VALORES (b), c = VALORES (c)

-4

Con PHP lo hice. Use punto y coma, divídalo en una matriz y luego envíelo por bucle.

$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(); 
-1

uso

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

Tenga en cuenta:

  • ID tiene que ser un primarias únicas claves
  • si utiliza claves ajenas a referencia la tabla, Reemplazar elimina luego inserta , entonces esto podría causar un error
19

Todo lo siguiente se aplica a InnoDB.

Siento que conocer las velocidades de los 3 métodos diferentes es importante.

Hay 3 métodos:

  1. INSERT: INSERT con ON DUPLICATE KEY UPDATE
  2. TRANSACCIÓN: Cuando se realiza una actualización para cada registro dentro de una transacción
  3. CASO: En la que un caso/cuando para cada registro diferente dentro de un UPDATE

Acabo de probar esto, y el método de inserción se 6.7x más rápido para mí que el TRAN Método de SACCIÓN Probé en un conjunto de 3.000 y 30.000 filas.

El método TRANSACTION aún tiene que ejecutar cada consulta individualmente, lo que lleva tiempo, aunque carga los resultados en la memoria, o algo así, durante la ejecución. El método TRANSACTION también es bastante caro en registros de replicación y de consulta.

Peor aún, el método CASE era 41.1x más lento que el método INSERT con 30,000 registros (6.1x más lento que TRANSACTION). Y 75x más lento en MyISAM. Los métodos INSERT y CASE se rompieron incluso en ~ 1,000 registros. Incluso en 100 registros, el método CASE es MUY más rápido.

Por lo tanto, en general, creo que el método INSERT es el mejor y el más fácil de usar. Las consultas son más pequeñas y fáciles de leer y solo ocupan 1 consulta de acción. Esto se aplica tanto a InnoDB como a MyISAM.

Bono cosas:

La solución para el problema del campo no predeterminado INSERT es desactivar temporalmente los modos SQL relevantes: SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TA‌​BLES",""),"STRICT_AL‌​L_TABLES",""). Asegúrese de guardar el sql_mode primero si planea revertirlo.

En cuanto a otros comentarios que he visto que dicen que el auto_incremento se incrementa con el método INSERT, lo probé y parece que no es el caso.

El código para ejecutar las pruebas es el siguiente. También genera archivos .SQL para eliminar php intérprete sobrecarga

<? 
//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).';'); 
} 
+0

Estás haciendo el trabajo del SEÑOR aquí;) Muy apreciado. – chili

0

¿Por qué nadie menciona varias instrucciones en una consulta?

En php, utiliza el método multi_query de la instancia de mysqli.

Desde el MySQL php manual

permite que opcionalmente tiene varias instrucciones en una serie de sentencia. El envío de varias instrucciones a la vez reduce los viajes redondos entre el cliente y el servidor, pero requiere un manejo especial.

Aquí está el resultado en comparación con otros 3 métodos en la actualización 30,000 en bruto. El código puede ser encontrado here que se basa en la respuesta de @Dakusan

Transacción: 5.5194580554962
Insertar: 0.20669293403625
caso: 16.474853992462
Multi: 0,0412278175354

Como se puede ver, múltiples instrucciones de consulta es más eficiente que la respuesta más alta.

Si recibe el mensaje de error como este:

PHP Warning: Error while sending SET_OPTION packet 

Es posible que necesite aumentar la max_allowed_packet en el archivo de configuración de MySQL, que en mi máquina es /etc/mysql/my.cnf y reinicie mysqld.

+0

Todas las comparaciones a continuación se ejecutan en contra de la prueba INSERTAR. Acabo de ejecutar la prueba en las mismas condiciones y, sin transacciones, fue * 145x * más lenta en 300 filas y * 753x * más lenta para 3000 filas. Originalmente comencé con las 30,000 filas, pero fui a almorzar y volví y todavía estaba funcionando. Esto tiene sentido ya que ejecutar consultas individuales y enjuagarlas a la base de datos individualmente sería ridículamente costoso. Especialmente con la replicación. Activar transacciones sin embargo hace una gran diferencia. En 3.000 filas, tomó * 1.5x * más y en 30,000 filas * 2.34x *. [continúa] – Dakusan

+0

Pero tenías razón de que es rápido (con transacciones). En ambas filas, de 3.000 a 30.000, fue más rápido que todo menos el método INSERT. No hay absolutamente ninguna manera de obtener mejores resultados ejecutando 1 consulta que 30,000 consultas, incluso si están agrupadas en una llamada especial API de MySQL. Al ejecutar solo 300 filas, fue MUCHO más rápido que todos los demás métodos (para mi sorpresa), que sigue aproximadamente la misma curva gráfica que el método CASE. Esto siendo más rápido se puede explicar de 2 maneras.La primera es que el método INSERT esencialmente siempre inserta 2 filas debido a "ON DUPLICATE KEY [cont] – Dakusan

+0

UPDATE" que causa un "INSERT" y "UPDATE". El otro es que es menos trabajo en el procesador SQL para solo editar 1 fila a la vez debido a las búsquedas de índice. No estoy seguro de cómo obtuviste resultados diferentes que yo, pero tu prueba adicional parece sólida. De hecho, ni siquiera estoy seguro de cómo la replicación manejaría esta llamada. Esto también funcionaría solo para hacer llamadas ACTUALIZADAS. Las llamadas de inserción SIEMPRE serán más rápidas con la única consulta INSERT. – Dakusan

Cuestiones relacionadas