在MySQL中交换列值

我有一个带坐标的MySQL表,列名是X和Y.现在我想交换表中的列值,以便X变成Y,Y变成X.最明显的解决方法是重命名列,但是我不想进行结构更改,因为我不一定有权限这样做。

这是可能的以某种方式更新吗? 更新表SET X = Y,Y = X显然不会做我想要的。


编辑:请注意,我对上述权限的限制,有效地防止使用ALTER TABLE或其他命令,改变表/数据库结构。 重命名列或添加新的列不幸是不可选的。

我只需要处理相同的问题,我将总结我的发现。

  1. UPDATE table SET X=Y, Y=X方法显然不起作用,因为它只会将这两个值都设置为Y.

  2. 这是一个使用临时variables的方法。 感谢安东尼从http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/评论“IS NOT NULL”的调整。 没有它,查询工作不可预测。 查看post末尾的表格模式。 如果其中一个为NULL,则此方法不交换值。 使用没有这个限制的方法#3。

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

  3. Dipin在http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/中提供了这种方法。; 我认为这是最优雅和干净的解决scheme。 它适用于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;

从本质上讲,第一个表是更新的第二个表,第二个表是用来从中提取旧的数据。
请注意,这种方法需要主键存在。

这是我的testing模式:

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

你可以用X和Y减去相反的数值

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

这是一个示例testing(它与负数工作)

 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=XY,X=XY; 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> 

试一试 !!!

以下代码适用于我的快速testing中的所有场景:

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

更新表设置X = Y,Y = X将正是你想要的(编辑:在PostgreSQL,而不是MySQL,见下文)。 这些值取自旧行,并分配给同一行的新副本,然后replace旧行。 您不必诉诸使用临时表,临时列或其他交换技巧。

// @ D4V360:我明白了。 这令人震惊和意外。 我使用PostgreSQL和我的答案在那里正确工作(我试过了)。 请参阅PostgreSQL UPDATE文档 (在Parameters,expression下),其中提到了SET子句右侧的expression式明确使用旧的列值。 我发现相应的MySQL UPDATE文档包含“单表UPDATE赋值通常从左到右计算”的语句,这暗示了您描述的行为。

很高兴知道。

好吧,只是为了好玩,你可以做到这一点! (假设你正在交换string值)

 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) 

在MySQL中滥用从左到右的评估过程很有趣。

或者,如果他们是数字,就使用XOR。 你提到的坐标,所以你有可爱的整数值,或复杂的string?

编辑:XOR的东西是这样工作的:

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

两种select1.使用临时表2.调查XORalgorithm

 ALTER TABLE table ADD COLUMN tmp;
 UPDATE表SET tmp = X;
 UPDATE表SET X = Y;
 UPDATE表SET Y = tmp;
 ALTER TABLE table DROP COLUMN tmp;

像这样的东西?

编辑:关于格雷格的评论:不,这不起作用:

 mysql> select * from test;
 + ------ + ------ +
 |  x |  y |
 + ------ + ------ +
 |  1 |  2 |
 |  3 |  4 |
 + ------ + ------ +
设置2行(0.00秒) 

mysql> update test set x = y,y = x;
查询OK,2行受影响(0.00秒)
行匹配:2更改:2警告:0

mysql> select * from test;
+ —— + —— +
| x | y |
+ —— + —— +
| 2 | 2 |
| 4 | 4 |
+ —— + —— +
设置2行(0.00秒)

我相信有一个中间交换variables是这样的最佳做法:

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

首先,它始终工作; 其次,它的工作原理与数据types无关。

尽pipe这两个

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

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

通常情况下,只是对于数字数据types而言,防止溢出是您的责任,您不能在有符号和无符号之间使用XOR,也不能使用和溢出的可能性。

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

如果c1是0或NULL或零长度的string或只是空格不工作。

我们需要改变它

 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) 

这肯定有效! 我只是需要它来交换欧元和SKK价格列。 🙂

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

以上将不起作用(错误1064(42000):您的SQL语法中有一个错误)

假设你已经在你的列中有整数,你可能需要使用CAST(a ^ b AS SIGNED),因为^运算符的结果是MySQL中的一个无符号的64位整数。

万一它有助于任何人,这里是我用来交换两个给定的行之间的同一列的方法:

 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 

其中$ 1和$ 2是两行的键,$ 3是第一个查询的结果。

我还没有尝试过,但是

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

可以这样做。

标记

可以改变列名,但这更多的是黑客。 但请谨慎对待可能在这些专栏上的任何索引

使用单个查询交换列值

UPDATE my_table SET a = @ tmp:= a,a = b,b = @ tmp;

干杯…!

我只需要将值从一列移动到另一列(如归档),并重置原始列的值。
下面(从上面接受的答案中引用的#3)为我工作。

 Update MyTable set X= (@temp:= X), X = 0, Y = @temp WHERE ID= 999; 
 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;