¿Cómo 'insertar si no existe' en MySQL?

838

Empecé buscando en Google y encontré este artículo que habla sobre tablas mutex.

Tengo una mesa con ~ 14 millones de registros. Si deseo agregar más datos en el mismo formato, ¿hay alguna manera de asegurar que el registro que deseo insertar no exista sin usar un par de consultas (es decir, una consulta para verificar y otra para insertar es el conjunto de resultados) vacío)?

¿Una uniquerestricción en un campo garantiza insertque fallará si ya está allí?

Parece que con solo una restricción, cuando publico el inserto a través de php, el script se rompe.

madriguera
fuente
Vea stackoverflow.com/questions/44550788/… para una discusión sobre no grabar valores de auto_inc.
Rick James
@RickJames - esa es una pregunta interesante ... pero no estoy seguro de que esté directamente relacionada con esta pregunta :)
warren
1
Se mencionó en un comentario, y esa otra pregunta afirmó que esta pregunta era un "duplicado exacto". Entonces, sentí que era una buena idea vincular las preguntas para beneficio de otros.
Rick James
1
Oh, nunca pienso mirar la barra lateral.
Rick James

Respuestas:

808

utilizar INSERT IGNORE INTO table

ver http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html

también hay INSERT … ON DUPLICATE KEY UPDATEsintaxis, puede encontrar explicaciones en dev.mysql.com


Publicación de bogdan.org.ua según el caché web de Google :

18 de octubre de 2007

Para comenzar: a partir de la última versión de MySQL, la sintaxis presentada en el título no es posible. Pero hay varias maneras muy fáciles de lograr lo que se espera usando la funcionalidad existente.

Hay 3 soluciones posibles: usar INSERT IGNORE, REPLACE o INSERT ... ON DUPLICATE KEY UPDATE.

Imagina que tenemos una mesa:

CREATE TABLE `transcripts` (
`ensembl_transcript_id` varchar(20) NOT NULL,
`transcript_chrom_start` int(10) unsigned NOT NULL,
`transcript_chrom_end` int(10) unsigned NOT NULL,
PRIMARY KEY (`ensembl_transcript_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Ahora imagine que tenemos una tubería automática que importa metadatos de transcripciones de Ensembl, y que debido a varias razones, la tubería puede romperse en cualquier paso de la ejecución. Por lo tanto, debemos garantizar dos cosas:

  1. ejecuciones repetidas de la tubería no destruirán nuestra base de datos

  2. las ejecuciones repetidas no morirán debido a errores de "clave principal duplicada".

Método 1: usando REPLACE

Es muy simple:

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Si el registro existe, se sobrescribirá; si aún no existe, se creará. Sin embargo, el uso de este método no es eficiente para nuestro caso: no necesitamos sobrescribir los registros existentes, está bien solo omitirlos.

Método 2: usando INSERT IGNORE También muy simple:

INSERT IGNORE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Aquí, si el 'ensembl_transcript_id' ya está presente en la base de datos, se omitirá (ignorará) en silencio. (Para ser más precisos, aquí hay una cita del manual de referencia de MySQL: “Si usa la palabra clave IGNORE, los errores que se producen al ejecutar la instrucción INSERT se tratan como advertencias. Por ejemplo, sin IGNORE, una fila que duplica un índice ÚNICO existente o el valor de PRIMARY KEY en la tabla causa un error de clave duplicada y la declaración se cancela. ”.) Si el registro aún no existe, se creará.

Este segundo método tiene varias debilidades potenciales, incluido el no aborto de la consulta en caso de que ocurra cualquier otro problema (consulte el manual). Por lo tanto, debe usarse si se probó previamente sin la palabra clave IGNORE.

Método 3: usando INSERTAR… EN ACTUALIZACIÓN CLAVE DUPLICADA:

La tercera opción es usar INSERT … ON DUPLICATE KEY UPDATE sintaxis, y en la parte ACTUALIZAR simplemente no hacer ninguna operación sin sentido (vacía), como calcular 0 + 0 (Geoffray sugiere hacer la asignación id = id para que el motor de optimización MySQL ignore esta operación). La ventaja de este método es que solo ignora los eventos clave duplicados y aún aborta en otros errores.

Como aviso final: esta publicación fue inspirada por Xaprb. También recomendaría consultar su otra publicación sobre cómo escribir consultas SQL flexibles.

knittl
fuente
3
¿y puedo combinar eso con "retrasado" para acelerar el script?
warren
3
Sí, insertar retrasado podría acelerar las cosas para usted. pruébalo
knittl
32
Sí, y tenga en cuenta que REPLACE INTO borra y luego INSERT, no ACTUALIZA
bobobobo
10
INSERT … ON DUPLICATE KEY UPDATEes mejor ya que no elimina la fila, conservando las auto_incrementcolumnas y otros datos.
redolente el
15
Solo para informar a todos. El uso del INSERT … ON DUPLICATE KEY UPDATEmétodo incrementa cualquier columna AUTO_INCREMENT con una inserción fallida. Probablemente porque no ha fallado realmente, pero ACTUALIZADO.
not2qubit
216

Solución:

INSERT INTO `table` (`value1`, `value2`) 
SELECT 'stuff for value1', 'stuff for value2' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1) 

Explicación:

La consulta más interna

SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1

utilizado como la WHERE NOT EXISTScondición detecta si ya existe una fila con los datos que se insertarán. Después de encontrar una fila de este tipo, la consulta puede detenerse, por lo tanto, elLIMIT 1 , se puede omitir la (microoptimización).

La consulta intermedia

SELECT 'stuff for value1', 'stuff for value2' FROM DUAL

representa los valores a insertar. DUALse refiere a una tabla especial de una fila y una columna presente de forma predeterminada en todas las bases de datos Oracle (consulte https://en.wikipedia.org/wiki/DUAL_table ). En un servidor MySQL versión 5.7.26 recibí una consulta válida al omitir FROM DUAL, pero las versiones anteriores (como 5.5.60) parecen requerir la FROMinformación. Mediante el usoWHERE NOT EXISTS la consulta intermedia, se devuelve un conjunto de resultados vacío si la consulta más interna encontró datos coincidentes.

La consulta externa

INSERT INTO `table` (`value1`, `value2`) 

inserta los datos, si alguno es devuelto por la consulta intermedia.

Servidor
fuente
44
¿Puedes dar más información sobre cómo usar esto?
Alex V
36
Esta variante es adecuada si no existe una clave única en la tabla ( INSERT IGNOREy INSERT ON DUPLICATE KEYrequiere restricciones de clave únicas)
rabudde
2
Si usa "desde dual" en la línea 2 en lugar de "desde la tabla", entonces no necesita la cláusula "límite 1".
Rico
66
¿Qué pasa si stuff for value1y stuff for value2son idénticos? Esto arrojaría unDuplicate column name
Robin
1
También prefiero mucho en SELECT 1lugar de SELECT *en las subconsultas. Es mucho más probable que esto pueda satisfacerse con un índice.
Arth
58

en la actualización de claves duplicadas , o insertar ignorar pueden ser soluciones viables con MySQL.


Ejemplo de actualización de actualización de clave duplicada basada en mysql.com

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;

Ejemplo de ignorar inserción basado en mysql.com

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

O:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    SET col_name={expr | DEFAULT}, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

O:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
Zed
fuente
24

Cualquier restricción simple debería hacer el trabajo, si una excepción es aceptable. Ejemplos:

  • clave principal si no es sustituto
  • restricción única en una columna
  • restricción única de varias columnas

Lo siento, esto parece engañosamente simple. Sé que se ve mal confrontado con el enlace que compartes con nosotros. ;-(

Pero nunca le doy esta respuesta, porque parece satisfacer su necesidad. (De lo contrario, puede activar la actualización de sus requisitos, lo que también sería "algo bueno").

Editado : si una inserción rompe la restricción única de la base de datos, se lanza una excepción a nivel de la base de datos, transmitida por el controlador. Ciertamente detendrá su script, con una falla. Debe ser posible en PHP abordar ese caso ...

KLE
fuente
1
Agregué una aclaración a la pregunta: ¿todavía se aplica su respuesta?
warren
2
Yo creo que si. Una restricción única causará la falla de inserciones incorrectas. Nota: tiene que lidiar con esta falla en su código, pero esto es bastante estándar.
KLE
1
por ahora me voy a quedar con la solución acepté - pero voy a mirar más allá en el manejo de fallos INSERT, etc. ya que la aplicación crece
Warren
3
INSERT IGNOREbásicamente cambia todos los errores en advertencias para que su secuencia de comandos no se interrumpa. Luego puede ver cualquier advertencia con el comando SHOW WARNINGS. Y otra nota importante : las restricciones ÚNICAS no funcionan con valores NULL, es decir. row1 (1, NULL) y row2 (1, NULL) se insertarán (a menos que se rompa otra restricción, como una clave primaria). Desgraciado.
Simon East
18

Aquí hay una función PHP que insertará una fila solo si todos los valores de las columnas especificadas no existen en la tabla.

  • Si una de las columnas difiere, se agregará la fila.

  • Si la tabla está vacía, se agregará la fila.

  • Si existe una fila donde todas las columnas especificadas tienen los valores especificados, la fila no se agregará.

    function insert_unique($table, $vars)
    {
      if (count($vars)) {
        $table = mysql_real_escape_string($table);
        $vars = array_map('mysql_real_escape_string', $vars);
    
        $req = "INSERT INTO `$table` (`". join('`, `', array_keys($vars)) ."`) ";
        $req .= "SELECT '". join("', '", $vars) ."' FROM DUAL ";
        $req .= "WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
    
        foreach ($vars AS $col => $val)
          $req .= "`$col`='$val' AND ";
    
        $req = substr($req, 0, -5) . ") LIMIT 1";
    
        $res = mysql_query($req) OR die();
        return mysql_insert_id();
      }
    
      return False;
    }

Ejemplo de uso:

<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>
Jrm
fuente
55
Bastante caro si tienes una gran carga de inserciones.
ad Дьdulяңмaи
cierto, pero eficiente si necesita agregar chequeos específicos
Charles Forest
1
Advertencia: la mysql_* extensión está en desuso a partir de PHP 5.5.0 y se ha eliminado a partir de PHP 7.0.0. En su lugar, se debe usar la extensión mysqli o PDO_MySQL . Consulte también la Descripción general de la API MySQL para obtener más ayuda al elegir una API MySQL.
Dharman
17
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Si el registro existe, se sobrescribirá; si aún no existe, se creará.

Rocio
fuente
10
REPLACEpuede eliminar la fila y luego insertar en lugar de actualizar. El efecto secundario es que las restricciones pueden eliminar otros objetos y se activan los desencadenantes de eliminación.
xmedeko
1
Del manual de MySQL: "REEMPLAZAR tiene sentido solo si una tabla tiene una CLAVE PRIMARIA o un índice ÚNICO. De lo contrario, se convierte en equivalente a INSERTAR, porque no hay índice para determinar si una nueva fila duplica a otra".
BurninLeo
16

Intenta lo siguiente:

IF (SELECT COUNT(*) FROM beta WHERE name = 'John' > 0)
  UPDATE alfa SET c1=(SELECT id FROM beta WHERE name = 'John')
ELSE
BEGIN
  INSERT INTO beta (name) VALUES ('John')
  INSERT INTO alfa (c1) VALUES (LAST_INSERT_ID())
END
Jeb's
fuente
55
Pruebe Estas respuestas son de poco valor en StackOverflow porque hacen muy poco para educar al OP y a miles de futuros investigadores. Edite esta respuesta para incluir cómo funciona la solución y por qué es una buena idea.
mickmackusa
1
¡Solución perfecta en caso de que los campos que coincidan no sean claves ...!
Leo
6

Hay varias respuestas que cubren cómo resolver esto si tiene un UNIQUEíndice que puede verificar con ON DUPLICATE KEYo INSERT IGNORE. Ese no es siempre el caso, y como UNIQUEtiene una restricción de longitud (1000 bytes), es posible que no pueda cambiar eso. Por ejemplo, tuve que trabajar con metadatos en WordPress (wp_postmeta ).

Finalmente lo resolví con dos consultas:

UPDATE wp_postmeta SET meta_value = ? WHERE meta_key = ? AND post_id = ?;
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) SELECT DISTINCT ?, ?, ? FROM wp_postmeta WHERE NOT EXISTS(SELECT * FROM wp_postmeta WHERE meta_key = ? AND post_id = ?);

La consulta 1 es una UPDATEconsulta normal sin efecto cuando el conjunto de datos en cuestión no está allí. La consulta 2 es una INSERTque depende de a NOT EXISTS, es decir, INSERTsolo se ejecuta cuando el conjunto de datos no existe.

Wortwart
fuente
3

Algo que vale la pena señalar es que INSERT IGNORE seguirá incrementando la clave principal si la declaración fue un éxito o no, como lo haría un INSERT normal.

Esto provocará lagunas en sus claves principales que podrían hacer que un programador sea mentalmente inestable. O si su aplicación está mal diseñada y depende de claves primarias incrementales perfectas, puede convertirse en un dolor de cabeza.

Examine innodb_autoinc_lock_mode = 0(configuración del servidor y viene con un ligero impacto en el rendimiento), o use un SELECCIONAR primero para asegurarse de que su consulta no falle (que también viene con un impacto en el rendimiento y un código adicional).

Gilly
fuente
¿Por qué los "vacíos en sus claves principales", incluso potencialmente, "hacen que un programador sea mentalmente inestable"? Las brechas se producen todo el tiempo en las claves principales, por ejemplo, cada vez que elimina un registro.
Warren
Comenzar con una SELECTderrota es el propósito de entregar un gran lote de correos INSERTelectrónicos y no querer preocuparse por los duplicados.
Warren
2

Actualizar o insertar sin clave primaria conocida

Si ya tiene una clave única o primaria, la otra responde con INSERT INTO ... ON DUPLICATE KEY UPDATE ...o REPLACE INTO ...debería funcionar bien (tenga en cuenta que reemplazar en elimina si existe y luego inserta, por lo que no actualiza parcialmente los valores existentes).

Pero si tiene los valores para some_column_idy some_type, cuya combinación se sabe que es única. Y desea actualizar some_valuesi existe o insertar si no existe. Y desea hacerlo en una sola consulta (para evitar el uso de una transacción). Esta podría ser una solución:

INSERT INTO my_table (id, some_column_id, some_type, some_value)
SELECT t.id, t.some_column_id, t.some_type, t.some_value
FROM (
    SELECT id, some_column_id, some_type, some_value
    FROM my_table
    WHERE some_column_id = ? AND some_type = ?
    UNION ALL
    SELECT s.id, s.some_column_id, s.some_type, s.some_value
    FROM (SELECT NULL AS id, ? AS some_column_id, ? AS some_type, ? AS some_value) AS s
) AS t
LIMIT 1
ON DUPLICATE KEY UPDATE
some_value = ?

Básicamente, la consulta se ejecuta de esta manera (menos complicado de lo que parece):

  • Seleccione una fila existente mediante la WHEREcoincidencia de cláusula.
  • Unión que resulta con una posible nueva fila (tabla s), donde los valores de columna se dan explícitamente (s.id es NULL, por lo que generará un nuevo identificador de incremento automático).
  • Si se encuentra una fila existente, la posible nueva fila de la tabla sse descarta (debido al LÍMITE 1 en la tabla t), y siempre activará una ON DUPLICATE KEYque será UPDATEla some_valuecolumna.
  • Si no se encuentra una fila existente, se inserta la nueva fila potencial (como se indica en la tabla s).

Nota: Cada tabla en una base de datos relacional debe tener al menos una idcolumna primaria de incremento automático . Si no tiene esto, agréguelo, incluso cuando no lo necesite a primera vista. Definitivamente es necesario para este "truco".

Yeti
fuente
Varios otros respondedores han ofrecido un INSERT INTO ... SELECT FROMformato. ¿Por qué tu también?
warren
2
@warren O no leíste mi respuesta, no la entiendes o no te la expliqué correctamente. En cualquier caso, permítanme enfatizar lo siguiente: esta no es solo una INSERT INTO... SELECT FROM...solución regular . Remítame un enlace a una respuesta que sea la misma, si puede encontrarla, eliminaré esta respuesta, de lo contrario, votó mi respuesta (¿trato?). Asegúrese de verificar que la respuesta que va a vincular solo usa 1 consulta (para actualización + inserción), ninguna transacción, y puede apuntar a cualquier combinación de columnas que se sepa que son únicas (por lo que las columnas por separado no necesita ser único).
Yeti