Dupliquer / copier des enregistrements dans la même table MySQL

87

Je cherche depuis un moment maintenant mais je ne trouve pas de solution simple à mon problème. Je voudrais dupliquer un enregistrement dans une table, mais bien sûr, la clé primaire unique doit être mise à jour.

J'ai cette question:

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

le problème est que cela modifie simplement IDla ligne au lieu de copier la ligne. Est-ce que quelqu'un sait comment réparer ceci ?

// edit: Je voudrais faire cela sans taper tous les noms de champs car les noms de champs peuvent changer avec le temps.

Chiffres
la source

Réponses:

137

La façon dont je procède habituellement consiste à utiliser une table temporaire. Ce n'est probablement pas efficace en termes de calcul, mais cela semble fonctionner correctement! Ici, je duplique l'enregistrement 99 dans son intégralité, créant l'enregistrement 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;

J'espère que cela fonctionne bien pour vous!

Alex
la source
J'aime ça, un pas au milieu pour garder un œil sur les choses
SeanDowney
4
Si vous copiez un seul enregistrement, vous pouvez déposer l'emplacement de mise à jour et d'insertion. Ensuite, vous pouvez simplement appuyer deux fois dans la console mysql pour afficher l'avant-dernière opération de l'historique, changer l'id dans la mise à jour, ce qui est pratique à la fin, appuyer sur Entrée, appuyer vers le haut, appuyer à nouveau sur Entrée sans rien changer, puis répéter la procédure pour plusieurs copies. Plus vite.
Cristian Vrabie
4
que faire au cas où l'id 100 est déjà là. Deuxièmement, si vous choisissez un nouvel identifiant pour votre nouvelle ligne, et pendant l'opération, quelqu'un d'autre insère une nouvelle ligne, puis votre identifiant et l'identifiant nouvellement inséré seront les mêmes. C'est le goulot de bouteille dans ce code.
nbhatti2001
6
Vous pouvez utiliser 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

La réponse d'Alex nécessite quelques précautions (ex. Verrouillage ou transaction) dans les environnements multi-clients.

En supposant que le AUTO IDchamp est le premier de la table (un cas habituel), nous pouvons utiliser des transactions implicites.

    CREATE TEMPORARY TABLE tmp SELECT * à partir des factures WHERE ...;
    ALTER TABLE tmp drop ID; # drop champ d'auto-incrémentation
    # UPDATE tmp SET ...; # juste besoin de changer d'autres clés uniques
    INSERT INTO factures SELECT 0, tmp. * FROM tmp;
    DROP TABLE tmp;

À partir de la documentation MySQL:

Utilisation de AUTO_INCREMENT: Vous pouvez également affecter explicitement NULL ou 0 à la colonne pour générer des numéros de séquence.

Tim Ruehsen
la source
Cela a mis l'enregistrement avec un ID de 0 pour moi, même si j'ai un champ AUTO_INCREMENT. Le changer en null a fonctionné, donc je vous suggère de changer le code pour utiliser null au lieu de 0. Je suis sur MySQL 5.5.35 (Ubuntu).
Tyler Collier
@TylerCollier: NULLou les 0deux sont acceptables. Pouvez-vous publier des captures d'écran du problème en le reproduisant et en le partageant ici.
Ravinder Reddy
J'ai mal au cerveau en essayant de comprendre la sous-requête neato à la ligne 4. Peut-être que cette explication peut aider: La table de destination invoicescontient un IDchamp AUTO_INCREMENT . La table source tmpne le fait pas. Afin d'obtenir la corrélation 1: 1 nécessaire lors de l'insertion, vous spécifiez a 0comme premier champ de la sélection, qui est le champ AUTO_INCREMENT de la table de destination. Le tmp.*sélectionne tous les champs de la table source tmp. Parfait! Vous avez maintenant votre corrélation 1: 1. Réponse géniale @Tim.
elbowlobstercowstand
@RavinderReddy NULL et 0 ne sont pas les mêmes - il est possible d'avoir un identifiant d'enregistrement de 0 - utilisez NULL pour ce genre de chose
Tobias Beuving
@TobiasBeuving : J'en suis bien conscient NULLet ce 0n'est pas la même chose. Le contexte actuel est sur la AUTO_INCREMENTfonctionnalité. Vous ne pouvez pas insérer 0une NULLvaleur ou une valeur dans ces champs. Mais lorsque vous les utilisez comme entrées dans ce champ, la prochaine valeur de la séquence sera attribué à ce champ auto matiquement. Et d'où mon commentaire. Preuve sur SQL Fiddle: sqlfiddle.com/#!9/d619f/1
Ravinder Reddy
12

Vous SAVEZ avec certitude que la TOUCHE DUPLICATE se déclenchera, vous pouvez donc sélectionner le MAX (ID) +1 au préalable:

INSERT INTO invoices SELECT MAX(ID)+1, ... other fields ... FROM invoices AS iv WHERE iv.ID=XXXXX 
Ingo
la source
2
oui, mais je ne veux pas taper tous les autres champs (il y en a plus ou moins 20, et ils peuvent changer avec le temps). Je ne veux pas changer le code à chaque fois que le tablestructre change.
Chiffres du
Tu devras. Vous feriez mieux de créer un script qui génère l'instruction SQL à partir d'une liste de champs. C'est trivial.
Ingo
La seule autre alternative est d'utiliser un déclencheur d'insertion (si mysql le prend en charge).
Ingo
11

Votre approche est bonne mais le problème est que vous utilisez "*" à la place en enrôlant les noms de champs. Si vous mettez tous les noms de colonnes sauf la clé primaire, votre script fonctionnera comme un charme sur un ou plusieurs enregistrements.

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

Un carré parfait
la source
Cependant, cela signifie que vous devrez changer le code dès qu'un champ est ajouté ou supprimé de la table, ce que je trouve personnellement MAJEUR non-non. (Pas toujours, mais assez souvent.)
Roemer
9

Une réponse tardive que je connais, mais c'est toujours une question courante, je voudrais ajouter une autre réponse qui a fonctionné pour moi, avec seulement une insert intoinstruction sur une seule ligne , et je pense que c'est simple, sans créer de nouveau tableau (car il pourrait être un problème avec les CREATE TEMPORARY TABLEautorisations):

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 solution fonctionne pour la AUTO_INCREMENTcolonne id, sinon, vous pouvez également ajouter une IDcolonne à l'instruction:

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;

C'est vraiment simple et direct, vous pouvez mettre à jour n'importe quoi d'autre en une seule ligne sans aucune deuxième instruction de mise à jour pour plus tard, (ex: mettre à jour une colonne de titre avec du texte supplémentaire ou remplacer une chaîne par une autre), vous pouvez également être précis avec quoi exactement vous voulez dupliquer, si tout est alors, si certains, vous pouvez le faire.

Al-Mothafar
la source
2
C'est également une bonne base pour modifier certains champs lors de la copie. Je viens INSERT into wp_options SELECT NULL, 'theme_mods_twopress', option_value, autoload FROM wp_options where option_name = 'theme_mods_onepress';de copier tout en augmentant l'entrée ( AUTO_INCREMENT NULLastuce d'en haut) et le option_namechamp également.
Marcel Waldvogel
Oui .. Bonne idée pour moi. Merci!!
Ragu Natarajan
3

Je voulais juste étendre la bonne réponse d'Alex pour la rendre appropriée si vous souhaitiez dupliquer un ensemble complet d'enregistrements:

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

Je devais juste faire cela et j'ai trouvé la réponse d'Alex un point de départ parfait !. Bien sûr, vous devez définir @x sur le numéro de ligne le plus élevé du tableau (je suis sûr que vous pouvez le saisir avec une requête). Ceci n'est utile que dans cette situation très spécifique, donc soyez prudent lorsque vous ne souhaitez pas dupliquer toutes les lignes. Ajustez les calculs si nécessaire.

bcsteeve
la source
2

J'ai un problème similaire, et c'est ce que je fais:

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

Été 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 ;

Donc, le IDest automatiquement incrémenté et j'utilise également une valeur fixe ('23') pour EncuestaID.

Alex Angelico
la source
désolé, je viens de réaliser que vous ne voulez pas utiliser le nom des colonnes, donc cela ne s'applique pas
Alex Angelico
1

J'en avais besoin aussi; ma solution était d'utiliser SQLYOG (version gratuite) pour exporter l'enregistrement souhaité en SQL (crée une insertion).

J'ai ensuite édité ceci à la main pour supprimer l'identifiant car il doit être généré automatiquement, puis copié l'insert dans SQLYog pour l'exécuter. C'était indolore. Je suppose que de nombreuses autres interfaces graphiques MySQL peuvent également le faire.

Cela me fournit un enregistrement que je peux utiliser à des fins de test sur un système en direct.

J'ai maintenant cet insert pour réutilisation, car le tableau est réécrit quotidiennement.

zzapper
la source
0

Légère variation, la principale différence étant de définir le champ de clé primaire ("varname") sur null, ce qui produit un avertissement mais fonctionne. En définissant la clé primaire sur null, l'incrémentation automatique fonctionne lors de l'insertion de l'enregistrement dans la dernière instruction.

Ce code nettoie également les tentatives précédentes et peut être exécuté plus d'une fois sans problème:

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
la source