2008-09-01 59 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;