Duplicar / copiar registros na mesma tabela MySQL

87

Já faz um tempo que estou procurando, mas não consigo encontrar uma solução fácil para o meu problema. Eu gostaria de duplicar um registro em uma tabela, mas é claro, a chave primária exclusiva precisa ser atualizada.

Eu tenho esta dúvida:

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

o problema é que isso apenas altera o IDda linha em vez de copiar a linha. Alguém sabe como consertar isso?

// editar: eu gostaria de fazer isso sem digitar todos os nomes de campo porque os nomes de campo podem mudar com o tempo.

Dígitos
fonte

Respostas:

137

A maneira que costumo fazer é usando uma mesa temporária. Provavelmente não é computacionalmente eficiente, mas parece funcionar bem! Aqui, estou duplicando o registro 99 por completo, criando o 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 bem para você!

Alex
fonte
Eu gosto, um passo no meio para ficar de olho nas coisas
SeanDowney
4
Se você estiver copiando um único registro, poderá ignorar o where tanto na atualização quanto na inserção. Então você poderia simplesmente pressionar duas vezes no console mysql, o que traria a penúltima operação no histórico, alterar o id na atualização que é conveniente no final, pressione enter, pressione para cima, pressione Enter novamente sem alterar nada e repita o procedimento para cópias múltiplas. Muito mais rapido.
Cristian Vrabie
4
e no caso de id 100 já estar lá. Secondlty se você escolher qualquer novo id para sua nova linha e, durante a operação, outra pessoa inserir uma nova linha e então seu id e o id recém-inserido serão os mesmos. Este é o gargalo deste código.
nbhatti2001
6
Você pode 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
@TobiasBeuvingColumn 'id' connot be null
Marcel
83

A resposta de Alex requer alguns cuidados (por exemplo, bloqueio ou uma transação) em ambientes multi-cliente.

Supondo que o AUTO IDcampo seja o primeiro da tabela (um caso comum), podemos fazer uso de transações implícitas.

    CRIAR TABELA TEMPORÁRIA tmp SELECT * das faturas ONDE ...;
    ALTER TABLE tmp drop ID; # drop autoincrement field
    # ATUALIZAR tmp SET ...; # só precisava alterar outras chaves exclusivas
    INSERT INTO invoices SELECT 0, tmp. * FROM tmp;
    DROP TABLE tmp;

Dos documentos do MySQL:

Usando AUTO_INCREMENT: Você também pode atribuir explicitamente NULL ou 0 à coluna para gerar números de sequência.

Tim Ruehsen
fonte
Isso coloca o registro com um ID de 0 para mim, embora eu tenha um campo AUTO_INCREMENT. Alterá-lo para nulo funcionou, então sugiro que você altere o código para usar nulo em vez de 0. Estou no MySQL 5.5.35 (Ubuntu).
Tyler Collier
@TylerCollier: NULLou 0ambos são aceitáveis. Você pode postar capturas de tela do problema reproduzindo e compartilhando aqui.
Ravinder Reddy
Meu cérebro doeu ao tentar entender a subconsulta neato na linha 4. Talvez esta explicação ajude: A tabela de destino invoicescontém um IDcampo AUTO_INCREMENT . A tabela de origem tmpnão. Para obter a correlação 1: 1 necessária durante a inserção, você especifica a 0como o primeiro campo da seleção, que é o campo AUTO_INCREMENT da tabela de destino. O tmp.*seleciona todos os campos da tabela de origem tmp. Perfeito! Agora você tem sua correlação 1: 1. Resposta incrível @Tim.
elbowlobstercowstand
@RavinderReddy NULL e 0 não são iguais - é possível ter um id de registro de 0 - use NULL para esse tipo de coisa
Tobias Beuving
@TobiasBeuving : Estou bem ciente disso NULLe 0não são iguais. O contexto atual está AUTO_INCREMENTativado. Você não pode inserir um 0ou um NULLvalor em tais campos. Mas quando você usá-los como insumos para tal campo, próximo valor seqüência será atribuído a esse campo auto maticamente. E daí meu comentário. Prova no SQL Fiddle: sqlfiddle.com/#!9/d619f/1
Ravinder Reddy
12

Você SABE com certeza que a CHAVE DUPLICADA irá disparar, portanto, você pode selecionar o MAX (ID) +1 de antemão:

INSERT INTO invoices SELECT MAX(ID)+1, ... other fields ... FROM invoices AS iv WHERE iv.ID=XXXXX 
Ingo
fonte
2
sim, mas não quero digitar todos os outros campos (são mais ou menos 20 e podem mudar com o tempo). Eu não quero mudar o código toda vez que o tablestructre muda.
Dígitos de
Você terá que. É melhor você fazer um script que gere a instrução SQL a partir de uma lista de campos. É trivial.
Ingo,
A única outra alternativa é usar um gatilho de inserção (se mysql suportar isso).
Ingo,
11

Sua abordagem é boa, mas o problema é que você usa "*" em vez de listar nomes de campos. Se você colocar todos os nomes de colunas, exceto a chave primária, seu script funcionará perfeitamente em um ou mais 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

Quadrado perfeito
fonte
No entanto, isso significa que você terá que alterar o código assim que um campo for adicionado ou eliminado da tabela, o que eu pessoalmente acho um NÃO PRINCIPAL. (Nem sempre, mas com frequência suficiente.)
Roemer
9

Uma resposta tardia, eu sei, mas ainda é uma pergunta comum, gostaria de adicionar outra resposta que funcionou para mim, usando apenas uma insert intoinstrução de uma linha , e eu acho que é simples, sem criar nenhuma nova tabela (uma vez que poderia ser um problema com as CREATE TEMPORARY TABLEpermissões):

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;

A solução está funcionando para a AUTO_INCREMENTcoluna id, caso contrário, você também pode adicionar a IDcoluna à instrução:

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;

É realmente fácil e direto, você pode atualizar qualquer coisa em uma única linha sem qualquer segunda instrução de atualização para mais tarde (ex: atualizar uma coluna de título com texto extra ou substituir uma string por outra), também você pode ser específico com o que exatamente você deseja duplicar, se tudo for, se houver, você pode fazer isso.

Al-Mothafar
fonte
2
Essa também é uma boa base para modificar alguns campos durante a cópia. Eu costumava INSERT into wp_options SELECT NULL, 'theme_mods_twopress', option_value, autoload FROM wp_options where option_name = 'theme_mods_onepress';copiar enquanto aumentava a entrada ( AUTO_INCREMENT NULLtruque acima) e o option_namecampo também.
Marcel Waldvogel de
Sim .. Boa ideia para mim. Obrigado!!
Ragu Natarajan
3

Eu só queria estender a ótima resposta de Alex para torná-la apropriada caso você queira duplicar um conjunto inteiro de registros:

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

Eu só tinha que fazer isso e achei a resposta de Alex um ponto de partida perfeito! Obviamente, você deve definir @x como o número de linha mais alto da tabela (tenho certeza de que você poderia pegar isso com uma consulta). Isso só é útil nesta situação muito específica, portanto, tome cuidado ao usá-lo quando não quiser duplicar todas as linhas. Ajuste a matemática conforme necessário.

bcsteeve
fonte
2

Tenho um problema semelhante, e isto é o que estou fazendo:

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

Been 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 ;

Portanto, o IDé incrementado automaticamente e também estou usando um valor fixo ('23') para EncuestaID.

Alex Angelico
fonte
desculpe, acabei de perceber que você não quer usar o nome das colunas, então isso não se aplica
Alex Angelico
1

Eu precisava disso também; minha solução foi usar SQLYOG (versão gratuita) para exportar o registro desejado como SQL (cria uma inserção).

Em seguida, editei manualmente para remover o id, pois ele precisa ser gerado automaticamente e copiei a inserção no SQLYog para executá-lo. Isso foi indolor. Acho que muitas outras GUIs do MySQL também podem fazer isso.

Isso me fornece um registro que posso usar para fins de teste em um sistema ativo.

Agora também tenho este encarte para reutilização, pois a tabela é reescrita diariamente.

zzapper
fonte
0

Ligeira variação, a principal diferença sendo definir o campo de chave primária ("varname") como nulo, o que produz um aviso, mas funciona. Ao definir a chave primária como nula, o incremento automático funciona ao inserir o registro na última instrução.

Este código também limpa as tentativas anteriores e pode ser executado mais de uma vez sem 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
fonte