Nhân bản / Sao chép bản ghi trong cùng một bảng MySQL


87

Tôi đã tìm kiếm một thời gian nhưng tôi không thể tìm thấy một giải pháp dễ dàng cho vấn đề của tôi. Tôi muốn sao chép bản ghi trong bảng, nhưng tất nhiên, khóa chính duy nhất cần được cập nhật.

Tôi có truy vấn này:

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

vấn đề là điều này chỉ thay đổi IDhàng thay vì sao chép hàng. Có ai biết cách sửa lỗi này không?

// chỉnh sửa: Tôi muốn thực hiện việc này mà không cần nhập tất cả các tên trường vì tên trường có thể thay đổi theo thời gian.

Câu trả lời:


137

Cách mà tôi thường làm là sử dụng một bảng tạm thời. Nó có thể không hiệu quả về mặt tính toán nhưng nó có vẻ hoạt động tốt! Ở đây tôi đang sao chép toàn bộ bản ghi 99, tạo bản ghi 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;

Hy vọng rằng hoạt động tốt cho bạn!


Tôi thích nó, một bước ở giữa để giữ một mắt vào những thứ
SeanDowney

4
Nếu bạn đang sao chép một bản ghi duy nhất, bạn có thể bỏ vị trí trong cả cập nhật và chèn. Sau đó, bạn chỉ cần nhấn lên hai lần trong bảng điều khiển mysql sẽ hiển thị thao tác tiếp theo của thao tác cuối cùng trong lịch sử, thay đổi id trong bản cập nhật thuận tiện ở cuối, nhấn enter, nhấn lên, nhấn enter lần nữa mà không thay đổi bất kỳ điều gì, sau đó lặp lại thủ tục cho nhiều bản sao. Nhanh hơn nhiều.
Cristian Vrabie

4
những gì trong trường hợp khi id 100 đã ở đó. Thứ hai nếu bạn chọn bất kỳ id mới nào cho hàng mới của mình và trong quá trình thao tác, người khác chèn một hàng mới và sau đó id của bạn và id mới được chèn sẽ giống nhau. Đây là cổ chai trong mã này có.
nbhatti2001

6
Bạn có thể sử dụng 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 Đó là thông báo lỗi đã sao chép vì tôi đã NOT NULLbật id. Tôi chỉ tự hỏi nếu bạn có gợi ý cho trường hợp này. Đó có được coi là hành vi xấu?
Marcel

84

Câu trả lời của Alex cần một số cẩn thận (ví dụ: khóa hoặc một giao dịch) trong môi trường nhiều khách hàng.

Giả sử AUTO IDtrường là trường đầu tiên trong bảng (trường hợp thông thường), chúng ta có thể sử dụng các giao dịch ngầm định.

    TẠO BẢNG TẠM THỜI Tmp SELECT * từ các hóa đơn WHERE ...;
    ALTER TABLE tmp thả ID; # thả trường autoincrement
    # CẬP NHẬT tmp SET ...; # chỉ cần để thay đổi các khóa duy nhất khác
    CHÈN VÀO hóa đơn CHỌN 0, tmp. * FROM tmp;
    DROP TABLE tmp;

Từ tài liệu MySQL:

Sử dụng AUTO_INCREMENT: Bạn cũng có thể chỉ định rõ ràng NULL hoặc 0 cho cột để tạo số thứ tự.


Điều này đặt bản ghi với ID là 0 cho tôi, mặc dù tôi có trường AUTO_INCREMENT. Thay đổi nó thành null đã hoạt động, vì vậy tôi khuyên bạn nên thay đổi mã để sử dụng null thay vì 0. Tôi đang sử dụng MySQL 5.5.35 (Ubuntu).
Tyler Collier

@TylerCollier: NULLhoặc 0cả hai đều được. Bạn có thể đăng ảnh chụp màn hình của vấn đề bằng cách tái tạo và chia sẻ nó trên đây.
Ravinder Reddy

Não tôi bị đau khi cố gắng hiểu truy vấn con neato ở dòng 4. Có thể giải thích này hữu ích: Bảng đích invoiceschứa IDtrường AUTO_INCREMENT . Bảng nguồn tmpkhông. Để có được mối tương quan 1: 1 cần thiết trong quá trình chèn, bạn chỉ định a 0làm trường đầu tiên của vùng chọn là trường AUTO_INCREMENT của bảng đích. Chọn tmp.*tất cả các trường từ bảng nguồn tmp. Hoàn hảo! Bây giờ bạn có mối tương quan 1: 1. Câu trả lời tuyệt vời @Tim.
elbowlobstercowstand

@RavinderReddy NULL và 0 là không giống nhau - đó là khả năng để có một kỷ lục id từ 0 - sử dụng NULL cho các loại điều này
Tobias Beuving

@TobiasBeuving : Tôi nhận thức rõ điều đó NULL0không giống nhau. Bối cảnh hiện tại là AUTO_INCREMENTtính năng. Bạn không thể chèn một 0hoặc một NULLgiá trị vào các trường như vậy. Nhưng khi bạn sử dụng chúng làm đầu vào cho trường như vậy, giá trị trình tự tiếp theo sẽ được gán tự động cho trường đó theo phương thức tĩnh. Và do đó nhận xét của tôi. Bằng chứng trên SQL Fiddle: sqlfiddle.com/#!9/d619f/1
Ravinder Reddy

12

Bạn BIẾT chắc chắn rằng KHÓA KÉP sẽ kích hoạt, do đó bạn có thể chọn trước TỐI ĐA (ID) +1:

INSERT INTO invoices SELECT MAX(ID)+1, ... other fields ... FROM invoices AS iv WHERE iv.ID=XXXXX 

2
có, nhưng tôi không muốn nhập tất cả các trường khác (có nhiều hơn hoặc ít hơn 20 và chúng có thể thay đổi theo thời gian). Tôi không muốn thay đổi mã mỗi khi cấu trúc tab thay đổi.
Chữ số

Bạn sẽ phải. Tốt hơn bạn nên tạo một tập lệnh tạo câu lệnh SQL từ danh sách các trường. Nó tầm thường.
Ingo

Cách thay thế duy nhất khác là sử dụng trình kích hoạt chèn (nếu mysql hỗ trợ điều đó).
Ingo

11

Cách tiếp cận của bạn là tốt nhưng vấn đề là bạn sử dụng "*" thay vì sử dụng tên trường. Nếu bạn đặt tất cả các tên cột ngoại trừ khóa chính, tập lệnh của bạn sẽ hoạt động giống như sự quyến rũ trên một hoặc nhiều bản ghi.

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


Tuy nhiên, điều đó có nghĩa là bạn sẽ phải thay đổi mã ngay sau khi một trường được thêm vào hoặc xóa khỏi bảng, điều mà cá nhân tôi thấy là KHÔNG CÓ. (Không phải lúc nào cũng vậy, nhưng thường xuyên là đủ.)
Roemer

10

Một câu trả lời muộn mà tôi biết, nhưng nó vẫn là một câu hỏi phổ biến, tôi muốn thêm một câu trả lời khác mà Nó phù hợp với tôi, chỉ sử dụng một insert intocâu lệnh dòng duy nhất và tôi nghĩ rằng nó rất đơn giản, không cần tạo bất kỳ bảng mới nào (vì nó có thể là một vấn đề với CREATE TEMPORARY TABLEquyền):

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;

Giải pháp đang hoạt động cho AUTO_INCREMENTcột id, nếu không, bạn cũng có thể thêm IDcột vào câu lệnh:

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;

Nó thực sự dễ dàng và đơn giản, bạn có thể cập nhật bất kỳ thứ gì khác trong một dòng duy nhất mà không cần bất kỳ câu lệnh cập nhật thứ hai nào sau này, (ví dụ: cập nhật cột tiêu đề với văn bản bổ sung hoặc thay thế một chuỗi bằng một chuỗi khác), bạn cũng có thể biết chính xác những gì bạn muốn sao chép, nếu tất cả thì có, nếu một số, bạn có thể làm như vậy.


2
Đây cũng là cơ sở tốt để sửa đổi một số trường trong khi sao chép. Tôi vừa sử dụng INSERT into wp_options SELECT NULL, 'theme_mods_twopress', option_value, autoload FROM wp_options where option_name = 'theme_mods_onepress';để sao chép vừa tăng mục nhập ( AUTO_INCREMENT NULLthủ thuật từ trên xuống) và cả option_nametrường nữa.
Marcel Waldvogel

Vâng .. Ý tưởng tốt cho tôi. Cảm ơn bạn!!
Ragu Natarajan

Giải pháp tốt nhất
Erlon Charles

3

Tôi chỉ muốn mở rộng câu trả lời tuyệt vời của Alex để làm cho nó phù hợp nếu bạn tình cờ muốn sao chép toàn bộ bộ hồ sơ:

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

Tôi chỉ cần làm điều này và thấy câu trả lời của Alex là một điểm xuất sắc hoàn hảo !. Tất nhiên, bạn phải đặt @x thành số hàng cao nhất trong bảng (tôi chắc chắn rằng bạn có thể lấy điều đó bằng một truy vấn). Điều này chỉ hữu ích trong tình huống rất cụ thể này, vì vậy hãy cẩn thận sử dụng nó khi bạn không muốn sao chép tất cả các hàng. Điều chỉnh toán học khi cần thiết.


2

Tôi gặp sự cố tương tự và đây là những gì tôi đang làm:

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 ;

Vì vậy, giá trị IDđược tăng tự động và tôi cũng đang sử dụng một giá trị cố định ('23') cho EncuestaID.


xin lỗi, tôi chỉ nhận ra bạn không muốn sử dụng tên cột, vì vậy điều này không áp dụng
Alex Angelico

1

Tôi cũng cần điều này; giải pháp của tôi là sử dụng SQLYOG (phiên bản miễn phí) để xuất bản ghi mong muốn dưới dạng SQL (tạo một chèn).

Sau đó, tôi đã chỉnh sửa thủ công này để xóa id vì id này cần được tạo tự động và sau đó sao chép chèn vào SQLYog để thực thi nó. Điều này là không đau. Tôi đoán nhiều GUI MySQL khác cũng có thể làm được điều này.

Điều này cung cấp cho tôi một bản ghi mà tôi có thể sử dụng cho mục đích kiểm tra trên một hệ thống trực tiếp.

Bây giờ tôi cũng có phụ trang này để sử dụng lại, vì bảng được viết lại hàng ngày.


0

Một chút biến thể, sự khác biệt chính là đặt trường khóa chính ("varname") thành null, tạo ra cảnh báo nhưng hoạt động. Bằng cách đặt khóa chính thành null, tính năng tự động tăng dần sẽ hoạt động khi chèn bản ghi vào câu lệnh cuối cùng.

Mã này cũng xóa các lần thử trước đó và có thể chạy nhiều lần mà không gặp sự cố:

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;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.