Duplikuj / kopiuj rekordy w tej samej tabeli MySQL

87

Szukałem już od jakiegoś czasu, ale nie mogę znaleźć łatwego rozwiązania mojego problemu. Chciałbym zduplikować rekord w tabeli, ale oczywiście unikalny klucz podstawowy wymaga aktualizacji.

Mam to zapytanie:

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

Problem w tym, że to po prostu zmienia IDwiersz zamiast go kopiować. Czy ktoś wie, jak to naprawić?

// edycja: Chciałbym to zrobić bez wpisywania wszystkich nazw pól, ponieważ nazwy pól mogą się zmieniać w czasie.

Cyfry
źródło

Odpowiedzi:

137

Sposób, w jaki zwykle to robię, polega na używaniu tymczasowej tabeli. Prawdopodobnie nie jest to wydajne obliczeniowo, ale wydaje się działać dobrze! Tutaj powielam rekord 99 w całości, tworząc rekord 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;

Mam nadzieję, że to działa dla Ciebie!

Alex
źródło
Podoba mi się, jeden krok w środku, aby mieć oko na rzeczy
SeanDowney
4
Jeśli kopiujesz pojedynczy rekord, możesz upuścić miejsce zarówno w aktualizacji, jak i wstawieniu. Następnie możesz po prostu nacisnąć dwa razy w górę w konsoli mysql, która przywoła przedostatnią operację w historii, zmienić identyfikator w aktualizacji, która jest wygodna na końcu, nacisnąć Enter, nacisnąć w górę, ponownie nacisnąć Enter bez zmiany czegokolwiek, a następnie powtórzyć procedura dla wielu kopii. O wiele szybciej.
Cristian Vrabie
4
co w przypadku, gdy id 100 już tam jest. Po drugie, jeśli wybierzesz jakiś nowy identyfikator do nowego wiersza, a podczas operacji ktoś inny wstawi nowy wiersz, a wtedy Twój identyfikator i nowo wstawiony identyfikator będą takie same. To jest szyjka butelki w tym kodzie.
nbhatti2001
6
Możesz użyć 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 To jest komunikat o błędzie kopiowaniem wklejony bo mam NOT NULLustawione na id. Zastanawiałem się tylko, czy masz sugestie dotyczące tego przypadku. Czy jest to uważane za złą praktykę?
Marcel,
84

Odpowiedź Alexa wymaga uwagi (np. Blokowanie lub transakcja) w środowiskach z wieloma klientami.

Zakładając, że AUTO IDpole jest pierwsze w tabeli (zwykły przypadek), możemy skorzystać z niejawnych transakcji.

    UTWÓRZ TABELĘ TYMCZASOWĄ tmp SELECT * z faktur GDZIE ...;
    ALTER TABLE tmp drop ID; # upuść pole autoincrement
    # UPDATE tmp SET ...; # wystarczyło zmienić inne unikalne klucze
    INSERT INTO faktury SELECT 0, tmp. * FROM tmp;
    DROP TABLE tmp;

Z dokumentacji MySQL:

Korzystanie z funkcji AUTO_INCREMENT: Możesz również jawnie przypisać NULL lub 0 do kolumny, aby wygenerować numery sekwencyjne.

Tim Ruehsen
źródło
Spowoduje to umieszczenie rekordu z identyfikatorem 0 dla mnie, mimo że mam pole AUTO_INCREMENT. Zmiana na null zadziałała, więc sugeruję zmianę kodu tak, aby używał null zamiast 0. Jestem na MySQL 5.5.35 (Ubuntu).
Tyler Collier,
@TylerCollier: NULLlub 0oba są dopuszczalne. Czy możesz opublikować zrzuty ekranu problemu, powielając je i udostępniając tutaj.
Ravinder Reddy
Mój mózg bolał, próbując zrozumieć podzapytanie neato w linii 4. Może to wyjaśnienie pomoże: tabela docelowa invoiceszawiera IDpole AUTO_INCREMENT . Tabela źródłowa tmpnie. Aby uzyskać wymaganą korelację 1: 1 podczas wstawiania, określasz 0jako pierwsze pole selekcji, które jest polem AUTO_INCREMENT tabeli docelowej. Do tmp.*wybiera wszystkie pola z tabeli źródłowej tmp. Idealny! Masz teraz swoją korelację 1: 1. Świetna odpowiedź @Tim.
elbowlobstercowstand
@RavinderReddy NULL i 0 to nie to samo - możliwe jest uzyskanie identyfikatora rekordu
równego
@TobiasBeuving : Jestem tego świadomy NULLi 0to nie to samo. Bieżący kontekst jest włączony AUTO_INCREMENT. W takich polach nie można wstawiać wartości 0ani NULL. Ale kiedy ich używać jako wejścia do takiego zakresu, następna wartość sekwencja zostanie przypisana do tego pola auto matycznie. I stąd mój komentarz. Dowód na SQL Fiddle: sqlfiddle.com/#!9/d619f/1
Ravinder Reddy
12

WIESZ na pewno, że DUPLICATE KEY zostanie wyzwolony, dlatego możesz wcześniej wybrać MAX (ID) +1:

INSERT INTO invoices SELECT MAX(ID)+1, ... other fields ... FROM invoices AS iv WHERE iv.ID=XXXXX 
Ingo
źródło
2
tak, ale nie chcę wpisywać wszystkich pozostałych pól (jest ich mniej więcej 20 i mogą się one zmieniać w czasie). Nie chcę zmieniać kodu za każdym razem, gdy zmienia się tabela.
Cyfry
Będziesz musiał. Lepiej zrób skrypt, który generuje instrukcję SQL z listy pól. To trywialne.
Ingo
Jedyną alternatywą jest użycie wyzwalacza wstawiania (jeśli mysql to obsługuje).
Ingo
11

Twoje podejście jest dobre, ale problem polega na tym, że zamiast wpisywania nazw pól używasz "*". Jeśli umieścisz wszystkie nazwy kolumn z wyjątkiem klucza podstawowego, skrypt będzie działał jak urok na jednym lub wielu rekordach.

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

Idealny kwadrat
źródło
Oznacza to jednak, że będziesz musiał zmienić kod, gdy tylko pole zostanie dodane lub usunięte z tabeli, co osobiście uważam za MAJORSKIE nie, nie. (Nie zawsze, ale często.)
Roemer
10

Późna odpowiedź, którą znam, ale wciąż jest to częste pytanie, chciałbym dodać kolejną odpowiedź, że zadziałało dla mnie, z użyciem tylko jednej insert intoinstrukcji liniowej i myślę, że jest to proste, bez tworzenia nowej tabeli (ponieważ być problemem z CREATE TEMPORARY TABLEuprawnieniami):

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;

Rozwiązanie działa dla AUTO_INCREMENTkolumny id, w przeciwnym razie możesz dodać IDkolumnę również do instrukcji:

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;

Jest to naprawdę łatwe i proste, możesz zaktualizować cokolwiek innego w jednym wierszu bez drugiego oświadczenia o aktualizacji na później (np. Zaktualizować kolumnę tytułu dodatkowym tekstem lub zastąpić ciąg innym), a także możesz dokładnie określić, co dokładnie chcesz powielić, jeśli wszystko to jest to, jeśli niektórzy, możesz to zrobić.

Al-Mothafar
źródło
2
Jest to również dobra podstawa do modyfikowania niektórych pól podczas kopiowania. Po prostu INSERT into wp_options SELECT NULL, 'theme_mods_twopress', option_value, autoload FROM wp_options where option_name = 'theme_mods_onepress';kopiowałem, zwiększając wpis ( AUTO_INCREMENT NULLsztuczka z góry) i option_namepole.
Marcel Waldvogel
Tak ... dla mnie dobry pomysł. Dziękuję Ci!!
Ragu Natarajan
Najlepsze rozwiązanie
Erlon Charles
3

Chciałem tylko rozszerzyć wspaniałą odpowiedź Alexa, aby była odpowiednia, jeśli chcesz zduplikować cały zestaw rekordów:

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

Po prostu musiałem to zrobić i uznałem odpowiedź Alexa za doskonały punkt wyjścia !. Oczywiście musisz ustawić @x na najwyższy numer wiersza w tabeli (jestem pewien, że możesz to złapać za pomocą zapytania). Jest to przydatne tylko w tej bardzo specyficznej sytuacji, więc zachowaj ostrożność, jeśli nie chcesz powielać wszystkich wierszy. W razie potrzeby dostosuj matematykę.

bcsteeve
źródło
2

Mam podobny problem i oto co robię:

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

Były 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 ;

Tak więc IDjest automatycznie zwiększana, a także używam stałej wartości („23”) EncuestaID.

Alex Angelico
źródło
przepraszam, właśnie zdałem sobie sprawę, że nie chcesz używać nazwy kolumn, więc to nie dotyczy
Alex Angelico
1

Ja też tego potrzebowałem; moim rozwiązaniem było użycie SQLYOG (wersja bezpłatna) do wyeksportowania żądanego rekordu jako SQL (tworzy wkładkę).

Następnie ręcznie zmodyfikowałem to, aby usunąć identyfikator, ponieważ musi on zostać wygenerowany automatycznie, a następnie skopiowałem wstawkę do SQLYog, aby go wykonać. To było bezbolesne. Myślę, że wiele innych graficznych interfejsów MySQL może to zrobić.

Dzięki temu otrzymuję rekord, którego mogę użyć do celów testowych w działającym systemie.

Teraz mam również tę wkładkę do ponownego wykorzystania, ponieważ tabela jest przepisywana codziennie.

zzapper
źródło
0

Niewielkie odchylenie, główna różnica polega na ustawieniu pola klucza podstawowego („nazwa_zmiennej”) na wartość null, co powoduje wyświetlenie ostrzeżenia, ale działa. Ustawiając klucz podstawowy na null, automatyczne zwiększanie wartości działa podczas wstawiania rekordu do ostatniej instrukcji.

Ten kod czyści również poprzednie próby i może być uruchamiany więcej niż raz bez problemów:

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
źródło