Duplizieren / Kopieren von Datensätzen in derselben MySQL-Tabelle

87

Ich habe jetzt eine Weile gesucht, aber ich kann keine einfache Lösung für mein Problem finden. Ich möchte einen Datensatz in einer Tabelle duplizieren, aber natürlich muss der eindeutige Primärschlüssel aktualisiert werden.

Ich habe diese Frage:

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

Das Problem ist, dass dies nur IDdie Zeile ändert, anstatt die Zeile zu kopieren. Weiß jemand, wie man das behebt?

// edit: Ich möchte dies tun, ohne alle Feldnamen einzugeben, da sich die Feldnamen im Laufe der Zeit ändern können.

Ziffern
quelle

Antworten:

137

Normalerweise verwende ich eine temporäre Tabelle. Es ist wahrscheinlich nicht rechnerisch effizient, aber es scheint in Ordnung zu funktionieren! Hier dupliziere ich Datensatz 99 in seiner Gesamtheit und erstelle Datensatz 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;

Hoffe das funktioniert ok für dich!

Alex
quelle
Ich mag es, einen Schritt in die Mitte, um die Dinge im Auge zu behalten
SeanDowney
4
Wenn Sie einen einzelnen Datensatz kopieren, können Sie das Where sowohl beim Aktualisieren als auch beim Einfügen löschen. Dann können Sie einfach zweimal in der MySQL-Konsole drücken, um den vorletzten Vorgang im Verlauf aufzurufen, die am Ende geeignete ID im Update ändern, die Eingabetaste drücken, drücken, erneut drücken, ohne etwas zu ändern, und dann wiederholen das Verfahren für mehrere Kopien. Viel schneller.
Cristian Vrabie
4
Was ist, wenn ID 100 bereits vorhanden ist? Zweitens, wenn Sie eine neue ID für Ihre neue Zeile auswählen und während des Vorgangs eine andere Person eine neue Zeile einfügt und Ihre ID und die neu eingefügte ID identisch sind. Dies ist der Flaschenhals in diesem Code.
nbhatti2001
6
Sie können NULL verwenden: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

Alex 'Antwort erfordert einige Sorgfalt (z. B. Sperren oder eine Transaktion) in Umgebungen mit mehreren Clients.

Angenommen, das AUTO IDFeld ist das erste in der Tabelle (ein normaler Fall), können wir implizite Transaktionen verwenden.

    CREATE TEMPORARY TABLE tmp SELECT * aus Rechnungen WHERE ...;
    ALTER TABLE tmp drop ID; # Autoincrement-Feld löschen
    # UPDATE tmp SET ...; # muss nur andere eindeutige Schlüssel ändern
    INSERT INTO Rechnungen SELECT 0, tmp. * FROM tmp;
    DROP TABLE tmp;

Aus den MySQL-Dokumenten:

Verwenden von AUTO_INCREMENT: Sie können der Spalte auch explizit NULL oder 0 zuweisen, um Sequenznummern zu generieren.

Tim Ruehsen
quelle
Dadurch wurde der Datensatz mit einer ID von 0 für mich eingegeben, obwohl ich ein Feld AUTO_INCREMENT habe. Das Ändern in null hat funktioniert, daher schlage ich vor, dass Sie den Code so ändern, dass null anstelle von 0 verwendet wird. Ich verwende MySQL 5.5.35 (Ubuntu).
Tyler Collier
@ TylerCollier: NULLoder 0beide sind akzeptabel. Können Sie Screenshots des Problems veröffentlichen, indem Sie es reproduzieren und hier weitergeben?
Ravinder Reddy
Mein Gehirn tat weh, als ich versuchte, die nette Unterabfrage in Zeile 4 zu verstehen. Vielleicht hilft diese Erklärung: Die Zieltabelle invoicesenthält ein IDFeld AUTO_INCREMENT . Die Quelltabelle tmpnicht. Um die erforderliche 1: 1-Korrelation während des Einfügens zu erhalten, geben Sie 0als erstes Feld der Auswahl a an, das das Feld AUTO_INCREMENT der Zieltabelle ist. Das tmp.*wählt alle Felder aus der Quelltabelle aus tmp. Perfekt! Sie haben jetzt Ihre 1: 1-Korrelation. Super Antwort @Tim.
Ellenbogenlobstercowstand
@ RavinderReddy NULL und 0 sind nicht dasselbe - es ist möglich, eine Datensatz-ID von 0 zu haben - verwenden Sie NULL für diese Art von Dingen
Tobias Beuving
@TobiasBeuving : Ich bin mir dessen bewusst NULLund bin 0nicht dasselbe. Der aktuelle Kontext ist AUTO_INCREMENTaktiviert. Sie können in solche Felder keinen 0oder einen NULLWert einfügen . Wenn Sie sie jedoch als Eingaben in ein solches Feld verwenden, wird diesem Feld automatisch der nächste Sequenzwert zugewiesen . Und daher mein Kommentar. Beweis auf SQL Fiddle: sqlfiddle.com/#!9/d619f/1
Ravinder Reddy
12

Sie wissen sicher, dass der DUPLICATE KEY ausgelöst wird, daher können Sie vorher MAX (ID) +1 auswählen:

INSERT INTO invoices SELECT MAX(ID)+1, ... other fields ... FROM invoices AS iv WHERE iv.ID=XXXXX 
Ingo
quelle
2
Ja, aber ich möchte nicht alle anderen Felder eingeben (es gibt mehr oder weniger 20, und sie können sich im Laufe der Zeit ändern). Ich möchte den Code nicht jedes Mal ändern, wenn sich das Tablestructre ändert.
Ziffern
Du musst. Erstellen Sie besser ein Skript, das die SQL-Anweisung aus einer Liste von Feldern generiert. Es ist trivial.
Ingo
Die einzige andere Alternative ist die Verwendung eines Insert-Triggers (wenn MySQL dies unterstützt).
Ingo
11

Ihr Ansatz ist gut, aber das Problem ist, dass Sie "*" verwenden, anstatt Feldnamen einzugeben. Wenn Sie alle Spaltennamen außer dem Primärschlüssel eingeben, funktioniert Ihr Skript wie ein Zauber auf einem oder mehreren Datensätzen.

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

Perfektes Viereck
quelle
Dies bedeutet jedoch, dass Sie den Code ändern müssen, sobald ein Feld zur Tabelle hinzugefügt oder daraus entfernt wird. Ich persönlich finde ein MAJOR no-no. (Nicht immer, aber oft genug.)
Roemer
9

Eine späte Antwort, die ich kenne, aber immer noch eine häufig gestellte Frage. Ich möchte eine weitere Antwort hinzufügen, die bei mir funktioniert hat, indem nur eine einzeilige insert intoAnweisung verwendet wurde, und ich denke, dass dies unkompliziert ist, ohne eine neue Tabelle zu erstellen (da dies möglich ist ein Problem mit CREATE TEMPORARY TABLEBerechtigungen sein):

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;

Die Lösung funktioniert für die AUTO_INCREMENTID-Spalte. Andernfalls können Sie IDder Anweisung auch eine Spalte hinzufügen :

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 ist wirklich einfach und unkompliziert. Sie können alles andere in einer einzelnen Zeile ohne eine zweite Aktualisierungsanweisung für später aktualisieren (z. B. Aktualisieren einer Titelspalte mit zusätzlichem Text oder Ersetzen einer Zeichenfolge durch eine andere). Außerdem können Sie genau angeben, mit was genau Sie möchten duplizieren, wenn alles, dann ist es, wenn einige, können Sie dies tun.

Al-Mothafar
quelle
2
Dies ist auch eine gute Grundlage, um einige Felder während des Kopierens zu ändern. Ich habe nur INSERT into wp_options SELECT NULL, 'theme_mods_twopress', option_value, autoload FROM wp_options where option_name = 'theme_mods_onepress';kopiert, während ich den Eintrag ( AUTO_INCREMENT NULLTrick von oben) und das option_nameFeld vergrößert habe .
Marcel Waldvogel
Ja .. Gute Idee für mich. Danke!!
Ragu Natarajan
3

Ich wollte nur Alex 'großartige Antwort erweitern, um sie angemessen zu machen, wenn Sie zufällig einen ganzen Satz von Datensätzen duplizieren möchten:

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

Ich musste das nur tun und fand Alex 'Antwort ein perfekter Ausgangspunkt!. Natürlich müssen Sie @x auf die höchste Zeilennummer in der Tabelle setzen (ich bin sicher, Sie könnten das mit einer Abfrage abrufen). Dies ist nur in dieser sehr speziellen Situation nützlich. Seien Sie also vorsichtig, wenn Sie nicht alle Zeilen duplizieren möchten. Passen Sie die Mathematik nach Bedarf an.

bcsteeve
quelle
2

Ich habe ein ähnliches Problem, und das mache ich:

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

Preguntas gewesen:

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 ;

Das IDwird also automatisch inkrementiert und ich verwende auch einen festen Wert ('23') für EncuestaID.

Alex Angelico
quelle
Entschuldigung, ich habe gerade festgestellt, dass Sie den Spaltennamen nicht verwenden möchten, daher gilt dies nicht
Alex Angelico
1

Ich brauchte das auch; Meine Lösung bestand darin, SQLYOG (kostenlose Version) zu verwenden, um den gewünschten Datensatz als SQL zu exportieren (erstellt eine Einfügung).

Ich habe dies dann von Hand bearbeitet, um die ID zu entfernen, da diese automatisch generiert werden muss, und dann die Einfügung in SQLYog kopiert, um sie auszuführen. Das war schmerzlos. Ich denke, viele andere MySQL-GUIs können dies ebenfalls.

Dadurch erhält ich eine Aufzeichnung, die ich für Testzwecke auf einem Live-System verwenden kann.

Ich habe diese Beilage jetzt auch zur Wiederverwendung, da die Tabelle täglich neu geschrieben wird.

zzapper
quelle
0

Leichte Abweichung, Hauptunterschied besteht darin, das Primärschlüsselfeld ("varname") auf null zu setzen, was eine Warnung erzeugt, aber funktioniert. Wenn Sie den Primärschlüssel auf null setzen, funktioniert die automatische Inkrementierung beim Einfügen des Datensatzes in die letzte Anweisung.

Dieser Code bereinigt auch frühere Versuche und kann ohne Probleme mehrmals ausgeführt werden:

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
quelle