Duplicar / Copiar registros en la misma tabla MySQL

87

He estado buscando por un tiempo pero no puedo encontrar una solución fácil para mi problema. Me gustaría duplicar un registro en una tabla, pero, por supuesto, es necesario actualizar la clave principal única.

Tengo esta consulta:

INSERT INTO invoices
    SELECT * FROM invoices AS iv WHERE iv.ID=XXXXX
    ON DUPLICATE KEY UPDATE ID = (SELECT MAX(ID)+1 FROM invoices)

el problema es que esto solo cambia el IDde la fila en lugar de copiar la fila. Sabe alguien cómo arreglar esto ?

// editar: me gustaría hacer esto sin escribir todos los nombres de los campos porque los nombres de los campos pueden cambiar con el tiempo.

Dígitos
fuente

Respuestas:

137

La forma en que suelo hacerlo es utilizando una tabla temporal. Probablemente no sea computacionalmente eficiente, ¡pero parece funcionar bien! Aquí estoy duplicando el registro 99 en su totalidad, creando el registro 100.

CREATE TEMPORARY TABLE tmp SELECT * FROM invoices WHERE id = 99;

UPDATE tmp SET id=100 WHERE id = 99;

INSERT INTO invoices SELECT * FROM tmp WHERE id = 100;

¡Espero que funcione bien para ti!

Alex
fuente
Me gusta, un paso en el medio para vigilar las cosas
SeanDowney
4
Si está copiando un solo registro, puede colocar el lugar en actualizar e insertar. Luego, puede presionar hacia arriba dos veces en la consola mysql, lo que mostrará la penúltima operación en el historial, cambiar la identificación en la actualización que es conveniente al final, presione enter, presione hacia arriba, presione enter nuevamente sin cambiar nada, luego repita el procedimiento para copias múltiples. Mucho mas rápido.
Cristian Vrabie
4
¿Qué pasa en el caso de que id 100 ya esté allí? En segundo lugar, si elige una nueva identificación para su nueva fila, y durante la operación, alguien más inserta una nueva fila y luego su identificación y la nueva identificación insertada serán las mismas. Este es el cuello de botella en este código.
nbhatti2001
6
Puede usar NULL:CREATE TEMPORARY TABLE tmp SELECT bla FROM invoices WHERE bla; UPDATE tmp SET id=NULL; INSERT INTO invoices SELECT * FROM tmp;
Tobias Beuving
1
@TobiasBeuving Ese es un mensaje de error copiado y pegado porque lo NOT NULLconfiguré id. Solo me preguntaba si tenía sugerencias para este caso. ¿Eso se considera una mala práctica?
Marcel
84

La respuesta de Alex necesita algo de cuidado (por ejemplo, bloqueo o una transacción) en entornos multicliente.

Suponiendo que el AUTO IDcampo es el primero de la tabla (un caso habitual), podemos hacer uso de transacciones implícitas.

    CREAR TABLA TEMPORAL tmp SELECCIONAR * de las facturas DONDE ...;
    ALTER TABLE tmp drop ID; # drop campo de autoincremento
    # ACTUALIZAR tmp SET ...; # solo necesitaba cambiar otras claves únicas
    INSERT INTO facturas SELECT 0, tmp. * FROM tmp;
    DROP TABLE tmp;

De los documentos de MySQL:

Usando AUTO_INCREMENT: También puede asignar explícitamente NULL o 0 a la columna para generar números de secuencia.

Tim Ruehsen
fuente
Esto puso el registro con un ID de 0 para mí, aunque tengo un campo AUTO_INCREMENT. Cambiarlo a nulo funcionó, así que le sugiero que cambie el código para usar nulo en lugar de 0. Estoy en MySQL 5.5.35 (Ubuntu).
Tyler Collier
@TylerCollier: NULLo 0ambos son aceptables. ¿Puedes publicar capturas de pantalla del problema reproduciéndolas y compartiéndolas aquí?
Ravinder Reddy
Me duele el cerebro al intentar comprender la subconsulta neato en la línea 4. Quizás esta explicación ayude: la tabla de destino invoicescontiene un IDcampo AUTO_INCREMENT . La tabla de origen tmpno lo hace. Para obtener la correlación necesaria 1: 1 durante la inserción, especifique a 0como primer campo de la selección, que es el campo AUTO_INCREMENT de la tabla de destino. Los tmp.*selecciona todos los campos de la tabla de origen tmp. ¡Perfecto! Ahora tiene su correlación 1: 1. Impresionante respuesta @Tim.
quiosco de langosta
@RavinderReddy NULL y 0 no son lo mismo - es posible tener un ID de registro de 0 - use NULL para este tipo de cosas
Tobias Beuving
@TobiasBeuving : Soy muy consciente de eso NULLy 0no somos lo mismo. El contexto actual está en AUTO_INCREMENTfunción. No puede insertar 0un NULLvalor o un valor en dichos campos. Pero cuando los usa como entradas en dicho campo, el siguiente valor de secuencia se asignará a ese campo automáticamente . Y de ahí mi comentario. Prueba en SQL Fiddle: sqlfiddle.com/#!9/d619f/1
Ravinder Reddy
12

Usted SABE con certeza que la DUPLICATE KEY se activará, por lo que puede seleccionar el MAX (ID) +1 de antemano:

INSERT INTO invoices SELECT MAX(ID)+1, ... other fields ... FROM invoices AS iv WHERE iv.ID=XXXXX 
Ingo
fuente
2
sí, pero no quiero escribir todos los demás campos (hay más o menos 20 y pueden cambiar con el tiempo). No quiero cambiar el código cada vez que cambia la estructura de la tabla.
Dígitos
Tendrás que. Será mejor que cree un script que genere la declaración SQL a partir de una lista de campos. Es trivial.
Ingo
La única otra alternativa es usar un activador de inserción (si mysql lo admite).
Ingo
11

Su enfoque es bueno, pero el problema es que usa "*" en lugar de dar de alta los nombres de los campos. Si coloca todos los nombres de las columnas excepto la clave principal, su script funcionará como el encanto en uno o varios registros.

INSERT INTO invoices (iv.field_name, iv.field_name,iv.field_name ) SELECT iv.field_name, iv.field_name,iv.field_name FROM invoices AS iv WHERE iv.ID=XXXXX

Cuadrado perfecto
fuente
Sin embargo, eso significa que tendrá que cambiar el código tan pronto como se agregue o elimine un campo de la tabla, lo que personalmente considero un NO MAYOR. (No siempre, pero con la frecuencia suficiente.)
Roemer
10

Una respuesta tardía que sé, pero sigue siendo una pregunta común, me gustaría agregar otra respuesta que funcionó para mí, con solo usar una insert intodeclaración de una sola línea , y creo que es sencillo, sin crear ninguna tabla nueva (ya que podría ser un problema con los CREATE TEMPORARY TABLEpermisos):

INSERT INTO invoices (col_1, col_2, col_3, ... etc)
  SELECT
    t.col_1,
    t.col_2,
    t.col_3,
    ...
    t.updated_date,
  FROM invoices t;

La solución funciona para la AUTO_INCREMENTcolumna de identificación; de lo contrario, también puede agregar una IDcolumna a la declaración:

INSERT INTO invoices (ID, col_1, col_2, col_3, ... etc)
  SELECT
    MAX(ID)+1,
    t.col_1,
    t.col_2,
    t.col_3,
    ... etc ,
  FROM invoices t;

Es realmente fácil y sencillo, puede actualizar cualquier otra cosa en una sola línea sin ninguna segunda declaración de actualización para más adelante (por ejemplo, actualizar una columna de título con texto adicional o reemplazar una cadena con otra), también puede ser específico con qué exactamente desea duplicar, si es todo, entonces, si lo es, puede hacerlo.

Al-Mothafar
fuente
2
Esta también es una buena base para modificar algunos campos durante la copia. Solo solía INSERT into wp_options SELECT NULL, 'theme_mods_twopress', option_value, autoload FROM wp_options where option_name = 'theme_mods_onepress';copiar mientras aumentaba la entrada ( AUTO_INCREMENT NULLtruco de arriba) y también el option_namecampo.
Marcel Waldvogel
Sí ... Buena idea para mí. ¡¡Gracias!!
Ragu Natarajan
La mejor solución
Erlon Charles
3

Solo quería extender la excelente respuesta de Alex para que sea apropiada si desea duplicar un conjunto completo de registros:

SET @x=7;
CREATE TEMPORARY TABLE tmp SELECT * FROM invoices;
UPDATE tmp SET id=id+@x;
INSERT INTO invoices SELECT * FROM tmp;

¡Solo tenía que hacer esto y encontré la respuesta de Alex como un punto de partida perfecto !. Por supuesto, debe establecer @x en el número de fila más alto de la tabla (estoy seguro de que podría obtenerlo con una consulta). Esto solo es útil en esta situación muy específica, así que tenga cuidado al usarlo cuando no desee duplicar todas las filas. Ajuste las matemáticas según sea necesario.

bcsteeve
fuente
2

Tengo un problema similar y esto es lo que estoy haciendo:

insert into Preguntas  (`EncuestaID`, `Tipo` , `Seccion` , `RespuestaID` , `Texto` )  select '23', `Tipo`, `Seccion`, `RespuestaID`, `Texto` from Preguntas where `EncuestaID`= 18

Sido Preguntas:

CREATE TABLE IF NOT EXISTS `Preguntas` (
  `ID` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `EncuestaID` int(11) DEFAULT NULL,
  `Tipo` char(5) COLLATE utf8_unicode_ci DEFAULT NULL,
  `Seccion` int(11) DEFAULT NULL,
  `RespuestaID` bigint(11) DEFAULT NULL,
  `Texto` text COLLATE utf8_unicode_ci ,
  PRIMARY KEY (`ID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=522 ;

Entonces, IDse incrementa automáticamente y también estoy usando un valor fijo ('23') para EncuestaID.

Alex Angelico
fuente
lo siento, me acabo de dar cuenta de que no quieres usar el nombre de las columnas, así que esto no se aplica
Alex Angelico
1

Yo también necesitaba esto; mi solución fue usar SQLYOG (versión gratuita) para exportar el registro deseado como SQL (crea una inserción).

Luego edité esto a mano para eliminar la identificación, ya que debe generarse automáticamente y luego copié la inserción en SQLYog para ejecutarla. Esto fue indoloro. Supongo que muchas otras GUI de MySQL también pueden hacer esto.

Esto me proporciona un registro que puedo usar con fines de prueba en un sistema en vivo.

Ahora también tengo este inserto para reutilizarlo, ya que la tabla se reescribe a diario.

zzapper
fuente
0

Ligera variación, la principal diferencia es establecer el campo de clave principal ("varname") en nulo, lo que produce una advertencia pero funciona. Al establecer la clave principal en nula, el incremento automático funciona al insertar el registro en la última declaración.

Este código también limpia los intentos anteriores y se puede ejecutar más de una vez sin problemas:

DELETE FROM `tbl` WHERE varname="primary key value for new record";
DROP TABLE tmp;
CREATE TEMPORARY TABLE tmp SELECT * FROM `tbl` WHERE varname="primary key value for old record";
UPDATE tmp SET varname=NULL;
INSERT INTO `tbl` SELECT * FROM tmp;
Nik Dow
fuente