2008-09-01 57 views
92

我有一個帶有座標的MySQL表,列名是X和Y.現在我想交換此表中的列值,以便X變成Y,Y變成X.最明顯的解決方案將重新命名列,但我不想進行結構更改,因爲我不一定有權這樣做。在MySQL中交換列值

這是可能以某種方式UPDATE辦? UPDATE表SET X = Y,Y = X顯然不會做我想要的。


編輯:請注意,我的權限限制,如上所述,有效地防止了使用ALTER TABLE或更改表/數據庫結構等命令。不幸的是,重命名列或添加新列不是選項。

+1

作爲一個註釋,'UPDATE表SET X = Y,Y = X`是在SQL中執行它的標準方式,只有MySQL不當行爲。 – 2016-09-15 08:25:47

回答

135

I just had to deal with the same and I'll summarize my findings.

  1. The UPDATE table SET X=Y, Y=X方法顯然是行不通的,因爲它會剛剛成立兩個值都爲Y.

  2. 這裏有一個方法使用臨時變量。感謝來自http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/對「IS NOT NULL」調整的評論中的Antony。沒有它,查詢的工作將不可預測。查看帖子末尾的表格模式。如果其中一個值爲NULL,則此方法不交換值。使用沒有此限制的方法#3。

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

  3. 這種方法是在主動提出Dipin,再次的http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/的意見。我認爲這是最優雅和乾淨的解決方案。它適用於NULL和非NULL值。

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

  4. 我想出了另一種方法似乎工作:

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

從本質上講,第一表得到更新的一個:第二個是用來從中提取舊數據。
請注意,此方法需要主鍵存在。

這是我的測試模式:

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); 
+15

正如在MySQL文檔中指出的那樣,在單個語句中分配和讀取變量是不安全的。操作順序無法保證。 所以唯一安全的方法是#4 – AMIB 2013-01-27 10:01:16

+0

選項4爲我工作。顯然,如果需要僅交換某些行的列,則可以在where子句中添加更多條件。 – 2013-03-01 18:51:08

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; 
Something like this?

Edit: About Greg's comment: No, this doesn't work:

 
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)

+0

只是爲了記錄:這個*確實*在PostgreSQL中工作,而它在* MySQL中不工作。 – str 2011-10-19 22:05:45

8

UPDATE table SET X=Y, Y=X will do precisely what you want (edit: in PostgreSQL, not MySQL, see below). The values are taken from the old row and assigned to a new copy of the same row, then the old row is replaced. You do not have to resort to using a temporary table, a temporary column, or other swap tricks.

@D4V360: I see. That is shocking and unexpected. I use PostgreSQL and my answer works correctly there (I tried it). See the PostgreSQL UPDATE docs (under Parameters, expression), where it mentions that expressions on the right hand side of SET clauses explicitly use the old values of columns. I see that the corresponding MySQL UPDATE docs contain the statement "Single-table UPDATE assignments are generally evaluated from left to right" which implies the behaviour you describe.

Good to know.

+0

感謝Greg和D4V360,很高興知道PostgreSQL和MySQL中有關更新查詢行爲的差異。 – 2008-10-25 16:41:35

+0

「x = y,y = x」方法也適用於Oracle,因爲它值得。 – 2012-10-17 16:52:47

+2

我用PostgreSQL和設置X = Y,Y = X救了我:) – Anonymous 2013-06-28 10:00:17

5

Ok, so just for fun, you could do this! (assuming you're swapping string values)

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) 

A nice bit of fun abusing the left-to-right evaluation process in MySQL.

Alternatively, just use XOR if they're numbers. You mentioned coordinates, so do you have lovely integer values, or complex strings?

Edit: The XOR stuff works like this by the way:

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

Assuming you have signed integers in your columns, you may need to use CAST(a^b AS SIGNED), since the result of the^operator is an unsigned 64-bit integer in MySQL.

In case it helps anyone, here's the method I used to swap the same column between two given rows:

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 

where $1 and $2 are the keys of two rows and $3 is the result of the first query.

2

This surely works! I've just needed it to swap Euro and SKK price columns. :)

UPDATE tbl SET X=Y, [email protected] where @temp:=X; 

The above will not work (ERROR 1064 (42000): You have an error in your SQL syntax)

18

下面的代碼適用於所有的場景在我的快速測試:

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

可能改變列名,但是這更多的是一種黑客攻擊。但要小心,可能是在這些列

30

你可以採取的總和,並使用X和減去相對值Y

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

下面是一個簡單的測試(並與負數的作品)任何指標

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> 

這裏是正在執行

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> 

試試看掉! !

0

使用列值的交換來實現單個查詢

UPDATE SET MY_TABLE一個= @ TMP:= A,A = B,B = @ TMP;

歡呼聲!

4

我相信有一箇中間交換變量是在這樣的方式的最佳做法:

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

首先,它的工作原理始終;其次,它的工作原理與數據類型無關。

儘管這兩種

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

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

通常的工作,只爲順便說一下數字數據類型,它是你的責任,以防止溢出,則不能使用之間的XOR簽名和無符號,你也不能使用和滿溢的可能性。

而且

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

不工作 如果c1爲0或NULL或零長度字符串或只是空間。

我們需要將其更改爲

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

下面是腳本:

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) 
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

我不得不正義之舉值從一列到其他(如存檔)和復位原始列的值。
以下(從上面接受的答案引用#3)爲我工作。

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