Trao đổi giá trị cột trong MySQL


127

Tôi có một bảng MySQL có tọa độ, tên cột là X và Y. Bây giờ tôi muốn hoán đổi các giá trị cột trong bảng này, để X trở thành Y và Y trở thành X. Giải pháp rõ ràng nhất là đổi tên các cột, nhưng tôi không muốn thay đổi cấu trúc vì tôi không nhất thiết phải có quyền để làm điều đó.

Điều này có thể làm với CẬP NHẬT theo một cách nào đó? Bảng CẬP NHẬT SET X = Y, Y = X rõ ràng sẽ không làm những gì tôi muốn.


Chỉnh sửa: Xin lưu ý rằng hạn chế của tôi đối với các quyền, được đề cập ở trên, ngăn chặn hiệu quả việc sử dụng ALTER TABLE hoặc các lệnh khác thay đổi cấu trúc bảng / cơ sở dữ liệu. Đổi tên cột hoặc thêm cột mới không may là tùy chọn.


5
như một lưu ý, UPDATE table SET X = Y, Y = Xlà cách làm tiêu chuẩn trong SQL, chỉ có các hành vi sai trái của MySQL.
Antti Haapala

Câu trả lời:


204

Tôi chỉ phải đối phó với điều tương tự và tôi sẽ tóm tắt những phát hiện của mình.

  1. Cách UPDATE table SET X=Y, Y=Xtiếp cận rõ ràng không hiệu quả, vì nó sẽ chỉ đặt cả hai giá trị thành Y.

  2. Đây là một phương pháp sử dụng một biến tạm thời. Cảm ơn Antony từ các bình luận của http://beerpla.net/2009/02/17/swicking-column-values-in-mysql/ cho chỉnh sửa "IS NOT NULL". Không có nó, truy vấn hoạt động không thể đoán trước. Xem lược đồ bảng ở cuối bài. Phương pháp này không trao đổi các giá trị nếu một trong số chúng là NULL. Sử dụng phương pháp # 3 không có giới hạn này.

    UPDATE swap_test SET x=y, y=@temp WHERE (@temp:=x) IS NOT NULL;

  3. Phương pháp này được Dipin đưa ra, một lần nữa, các ý kiến ​​của http://beerpla.net/2009/02/17/swicking-column-values-in-mysql/ . Tôi nghĩ đó là giải pháp thanh lịch và sạch sẽ nhất. Nó hoạt động với cả giá trị NULL và không NULL.

    UPDATE swap_test SET x=(@temp:=x), x = y, y = @temp;

  4. Một cách tiếp cận khác mà tôi nghĩ ra có vẻ hiệu quả:

    UPDATE swap_test s1, swap_test s2 SET s1.x=s1.y, s1.y=s2.x WHERE s1.id=s2.id;

Về cơ bản, bảng 1 là bảng được cập nhật và bảng thứ 2 được sử dụng để lấy dữ liệu cũ từ đó.
Lưu ý rằng phương pháp này đòi hỏi phải có khóa chính.

Đây là lược đồ thử nghiệm của tôi:

CREATE TABLE `swap_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `x` varchar(255) DEFAULT NULL,
  `y` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

INSERT INTO `swap_test` VALUES ('1', 'a', '10');
INSERT INTO `swap_test` VALUES ('2', NULL, '20');
INSERT INTO `swap_test` VALUES ('3', 'c', NULL);

25
Như đã lưu ý trong các tài liệu MySQL, việc gán và đọc các biến trong một câu lệnh không an toàn. Trình tự hoạt động không được đảm bảo. Vì vậy, phương pháp an toàn duy nhất là # 4
AMIB

Lựa chọn 4 làm việc cho tôi. Rõ ràng bạn có thể thêm nhiều điều kiện vào mệnh đề where nếu bạn cần hoán đổi các cột chỉ cho một số hàng.
Brad Campbell

7
Bạn biết đấy, tôi chưa bao giờ nghĩ rằng sẽ có một cách sử dụng thực tế cho câu hỏi phỏng vấn ngu ngốc đó yêu cầu hoán đổi hai biến mà không sử dụng tạm thời, nhưng ở đây, và đối với các số nguyên, điều này thực sự sẽ hoạt động: update update_test set x = x + y, y = xy, x = xy;
izak

Hầu hết câu trả lời này là sao chép / dán trực tiếp từ beerpla.net/2009/02/17/swicking-column-values-in-mysql

17
@Jhawins Đó là vì beerpla.net là blog của tôi.
Artem Russakovskii

52

Bạn có thể lấy tổng và trừ giá trị đối lập bằng X và Y

UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;

Đây là một bài kiểm tra mẫu (và nó hoạt động với các số âm)

mysql> use test
Database changed
mysql> drop table if exists swaptest;
Query OK, 0 rows affected (0.03 sec)

mysql> create table swaptest (X int,Y int);
Query OK, 0 rows affected (0.12 sec)

mysql> INSERT INTO swaptest VALUES (1,2),(3,4),(-5,-8),(-13,27);
Query OK, 4 rows affected (0.08 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM swaptest;
+------+------+
| X    | Y    |
+------+------+
|    1 |    2 |
|    3 |    4 |
|   -5 |   -8 |
|  -13 |   27 |
+------+------+
4 rows in set (0.00 sec)

mysql>

Đây là trao đổi đang được thực hiện

mysql> UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;
Query OK, 4 rows affected (0.07 sec)
Rows matched: 4  Changed: 4  Warnings: 0

mysql> SELECT * FROM swaptest;
+------+------+
| X    | Y    |
+------+------+
|    2 |    1 |
|    4 |    3 |
|   -8 |   -5 |
|   27 |  -13 |
+------+------+
4 rows in set (0.00 sec)

mysql>

Hãy thử một lần !!!


5
Đối với những con số đó thực sự là một con số gọn gàng nhất.
Ý thức chung của bạn

Có thể là một vấn đề nếu một giá trị tràn ra khi thêm?
ToolmakerSteve

@ToolmakerSteve có lẽ cho TINYINThoặc các giá trị lớn INT, bạn đã đúng !!!
RolandoMySQLDBA

29

Đoạn mã sau hoạt động cho tất cả các kịch bản trong thử nghiệm nhanh của tôi:

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp

UPDATE table swap_test? Có nên không UPDATE swap_test?
Pang

12

Bảng CẬP NHẬT SET X = Y, Y = X sẽ thực hiện chính xác những gì bạn muốn (chỉnh sửa: trong PostgreQuery, không phải MySQL, xem bên dưới). Các giá trị được lấy từ hàng cũ và được gán cho một bản sao mới của cùng một hàng, sau đó hàng cũ được thay thế. Bạn không cần phải sử dụng bảng tạm thời, cột tạm thời hoặc các thủ thuật hoán đổi khác.

@ D4V360: Tôi hiểu. Đó là điều gây sốc và bất ngờ. Tôi sử dụng PostgreSQL và câu trả lời của tôi hoạt động chính xác ở đó (tôi đã thử nó). Xem các tài liệu CẬP NHẬT PostgreSQL (bên dưới Tham số, biểu thức), trong đó đề cập đến các biểu thức ở phía bên phải của mệnh đề SET sử dụng rõ ràng các giá trị cũ của các cột. Tôi thấy rằng các tài liệu CẬP NHẬT MySQL tương ứng chứa câu lệnh "Các bài tập CẬP NHẬT bảng đơn thường được đánh giá từ trái sang phải" ngụ ý hành vi bạn mô tả.

Tốt để biết.


Cảm ơn Greg và D4V360, thật tốt khi biết sự khác biệt trong PostgreSQL và MySQL về hành vi của các truy vấn cập nhật.
Vijay Dev

Cách tiếp cận "x = y, y = x" cũng hoạt động trong Oracle, với giá trị của nó.
Burhan Ali

2
Tôi đã sử dụng PostgreSQL và SET X = Y, Y = X đã lưu tôi :)
Ẩn danh

4
IMHO câu trả lời này là một mớ hỗn độn - lời khuyên tồi với "oops never mind" được nối thêm. Một nửa trong số đó phải là một bình luận và phần duy nhất còn lại liên quan đến câu hỏi là liên kết đến tài liệu MySQL ...
Air

6

Ok, vì vậy chỉ để cho vui, bạn có thể làm điều này! (giả sử bạn đang hoán đổi giá trị chuỗi)

mysql> select * from swapper;
+------+------+
| foo  | bar  |
+------+------+
| 6    | 1    | 
| 5    | 2    | 
| 4    | 3    | 
+------+------+
3 rows in set (0.00 sec)

mysql> update swapper set 
    -> foo = concat(foo, "###", bar),
    -> bar = replace(foo, concat("###", bar), ""),
    -> foo = replace(foo, concat(bar, "###"), "");

Query OK, 3 rows affected (0.00 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> select * from swapper;
+------+------+
| foo  | bar  |
+------+------+
| 1    | 6    | 
| 2    | 5    | 
| 3    | 4    | 
+------+------+
3 rows in set (0.00 sec)

Một chút thú vị khi lạm dụng quy trình đánh giá từ trái sang phải trong MySQL.

Ngoài ra, chỉ cần sử dụng XOR nếu chúng là số. Bạn đã đề cập đến tọa độ, vậy bạn có các giá trị nguyên đáng yêu hay các chuỗi phức không?

Chỉnh sửa: Nhân tiện XOR hoạt động như thế này:

update swapper set foo = foo ^ bar, bar = foo ^ bar, foo = foo ^ bar;

5

Tôi tin rằng có một biến trao đổi trung gian là cách thực hành tốt nhất theo cách như vậy:

update z set c1 = @c := c1, c1 = c2, c2 = @c

Đầu tiên, nó hoạt động luôn; thứ hai, nó hoạt động bất kể loại dữ liệu.

Bất chấp cả hai

update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2

update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2

Nhân tiện, chỉ hoạt động cho loại dữ liệu số và bạn có trách nhiệm ngăn chặn tràn, bạn không thể sử dụng XOR giữa đã ký và chưa ký, bạn cũng không thể sử dụng tổng cho khả năng tràn.

update z set c1 = c2, c2 = @c where @c := c1

không hoạt động nếu c1 là 0 hoặc NULL hoặc chuỗi có độ dài bằng 0 hoặc chỉ khoảng trắng.

Chúng ta cần thay đổi nó thành

update z set c1 = c2, c2 = @c where if((@c := c1), true, true)

Đây là kịch bản:

mysql> create table z (c1 int, c2 int)
    -> ;
Query OK, 0 rows affected (0.02 sec)

mysql> insert into z values(0, 1), (-1, 1), (pow(2, 31) - 1, pow(2, 31) - 2)
    -> ;
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from z;
+------------+------------+
| c1         | c2         |
+------------+------------+
|          0 |          1 |
|         -1 |          1 |
| 2147483647 | 2147483646 |
+------------+------------+
3 rows in set (0.02 sec)

mysql> update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2;
ERROR 1264 (22003): Out of range value for column 'c1' at row 2
mysql> update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2;
ERROR 1264 (22003): Out of range value for column 'c1' at row 3

mysql> select * from z;
+------------+------------+
| c1         | c2         |
+------------+------------+
|          0 |          1 |
|          1 |         -1 |
| 2147483646 | 2147483647 |
+------------+------------+
3 rows in set (0.02 sec)

mysql> update z set c1 = c2, c2 = @c where @c := c1;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

mysql> select * from z;
+------------+------------+
| c1         | c2         |
+------------+------------+
|          0 |          1 |
|         -1 |          1 |
| 2147483647 | 2147483646 |
+------------+------------+
3 rows in set (0.00 sec)

mysql> select * from z;
+------------+------------+
| c1         | c2         |
+------------+------------+
|          1 |          0 |
|          1 |         -1 |
| 2147483646 | 2147483647 |
+------------+------------+
3 rows in set (0.00 sec)

mysql> update z set c1 = @c := c1, c1 = c2, c2 = @c;
Query OK, 3 rows affected (0.02 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> select * from z;
+------------+------------+
| c1         | c2         |
+------------+------------+
|          0 |          1 |
|         -1 |          1 |
| 2147483647 | 2147483646 |
+------------+------------+
3 rows in set (0.00 sec)

mysql>update z set c1 = c2, c2 = @c where if((@c := c1), true, true);
Query OK, 3 rows affected (0.02 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> select * from z;
+------------+------------+
| c1         | c2         |
+------------+------------+
|          1 |          0 |
|          1 |         -1 |
| 2147483646 | 2147483647 |
+------------+------------+
3 rows in set (0.00 sec)

+1 để cuối cùng tìm thấy cách sử dụng tốt cho câu hỏi phỏng vấn ngu ngốc khi bạn phải trao đổi hai biến mà không có tạm thời ;-)
izak


4

ALTER TABLE table ADD COLUMN tmp;
UPDATE table SET tmp = X;
UPDATE table SET X = Y;
UPDATE table SET Y = tmp;
ALTER TABLE table DROP COLUMN tmp;
Một cái gì đó như thế này?

Chỉnh sửa: Về nhận xét của Greg: Không, điều này không hoạt động:

mysql> select * from test;
+------+------+
| x    | y    |
+------+------+
|    1 |    2 |
|    3 |    4 |
+------+------+
2 rows in set (0.00 sec)

mysql> update test set x=y, y=x; Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0

mysql> select * from test; +------+------+ | x | y | +------+------+ | 2 | 2 | | 4 | 4 | +------+------+ 2 rows in set (0.00 sec)


Chỉ dành cho bản ghi: Điều này không hoạt động trong PostgreSQL trong khi nó không hoạt động trong MySQL.
str

2

Điều này chắc chắn hoạt động! Tôi chỉ cần nó để trao đổi cột giá Euro và SKK. :)

UPDATE tbl SET X=Y, Y=@temp where @temp:=X;

Những điều trên sẽ không hoạt động (ERROR 1064 (42000): Bạn có lỗi trong cú pháp SQL của mình)


1

Giả sử bạn đã ký các số nguyên trong các cột của mình, bạn có thể cần sử dụng CAST (a ^ b NHƯ KÝ), vì kết quả của toán tử ^ là một số nguyên 64 bit không dấu trong MySQL.

Trong trường hợp nó giúp được bất cứ ai, đây là phương pháp tôi đã sử dụng để trao đổi cùng một cột giữa hai hàng đã cho:

SELECT BIT_XOR(foo) FROM table WHERE key = $1 OR key = $2

UPDATE table SET foo = CAST(foo ^ $3 AS SIGNED) WHERE key = $1 OR key = $2

trong đó $ 1 và $ 2 là khóa của hai hàng và $ 3 là kết quả của truy vấn đầu tiên.


1

Tôi đã không thử nó nhưng

UPDATE tbl SET @temp=X, X=Y, Y=@temp

Có thể làm điều đó.

dấu


1

Bạn có thể thay đổi tên cột, nhưng đây là một vụ hack. Nhưng hãy thận trọng với bất kỳ chỉ mục nào có thể nằm trên các cột này


1

Tên bảng là khách hàng. các trường là a và b, hoán đổi một giá trị thành b;.

CẬP NHẬT khách hàng SET a = (@ temp: = a), a = b, b = @temp

Tôi đã kiểm tra điều này là hoạt động tốt.


1

Trong SQL Server, bạn có thể sử dụng truy vấn này:

update swaptable 
set col1 = t2.col2,
col2 = t2.col1
from swaptable t2
where id = t2.id


0

Tôi phải di chuyển giá trị từ cột này sang cột khác (như lưu trữ) và đặt lại giá trị của cột ban đầu.
Dưới đây (tham khảo số 3 từ câu trả lời được chấp nhận ở trên) đã làm việc cho tôi.

Update MyTable set X= (@temp:= X), X = 0, Y = @temp WHERE ID= 999;

0
CREATE TABLE Names
(
F_NAME VARCHAR(22),
L_NAME VARCHAR(22)
);

INSERT INTO Names VALUES('Ashutosh', 'Singh'),('Anshuman','Singh'),('Manu', 'Singh');

UPDATE Names N1 , Names N2 SET N1.F_NAME = N2.L_NAME , N1.L_NAME = N2.F_NAME 
WHERE N1.F_NAME = N2.F_NAME;

SELECT * FROM Names;

0

Ví dụ này hoán đổi start_dateend_date cho các bản ghi trong đó ngày sai vòng (khi thực hiện ETL thành một bản viết lại chính, tôi thấy một số ngày bắt đầu muộn hơn ngày kết thúc của chúng ngày . Xuống, lập trình viên tồi!).

Tại chỗ, tôi đang sử dụng MEDIUMINT vì lý do hiệu suất (như ngày Julian, nhưng có gốc 0 1900-01-01), vì vậy tôi đã ổn khi thực hiện điều kiện WHERE mdu.start_date> mdu.end_date .

Các PK nằm trên cả 3 cột riêng lẻ (vì lý do vận hành / lập chỉ mục).

UPDATE monitor_date mdu
INNER JOIN monitor_date mdc
    ON mdu.register_id = mdc.register_id
    AND mdu.start_date = mdc.start_date
    AND mdu.end_date = mdc.end_date
SET mdu.start_date = mdu.end_date, mdu.end_date = mdc.start_date
WHERE mdu.start_date > mdu.end_date;

FYI: Mã này đã cập nhật 145 / 108.456 hồ sơ trong 0,203 giây. Đó là một nhiệm vụ tắt và vì vậy hiệu suất không quan trọng.
Andrew Foster

0

Giả sử bạn muốn trao đổi giá trị của tên và họ trong tb_user.

An toàn nhất sẽ là:

  1. Sao chép tb_user. Vì vậy, bạn sẽ có 2 bảng: tb_user và tb_user_copy
  2. Sử dụng truy vấn CẬP NHẬT VÀO
UPDATE tb_user a
INNER JOIN tb_user_copy b
ON a.id = b.id
SET a.first_name = b.last_name, a.last_name = b.first_name

0

Bạn có thể áp dụng truy vấn dưới đây, Nó hoạt động hoàn hảo cho tôi.

Table name: studentname
only single column available: name


update studentnames 
set names = case names 
when "Tanu" then "dipan"
when "dipan" then "Tanu"
end;

or

update studentnames 
set names = case names 
when "Tanu" then "dipan"
else "Tanu"
end;
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.