在MySQL中交换列值

Lie*_*man 121 mysql database

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

这有可能以某种方式与UPDATE有关吗?UPDATE表SET X = Y,Y = X显然不会做我想要的.


编辑:请注意,我对上述权限的限制有效地阻止了使用ALTER TABLE或其他更改表/数据库结构的命令.遗憾的是,重命名列或添加新列不是选项.

Art*_*kii 188

我只需处理相同的事情,我将总结我的发现.

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

  2. 这是一个使用临时变量的方法.感谢Antony在http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/的评论中对"IS NOT NULL"的调整.没有它,查询工作无法预测.请参阅帖子末尾的表架构.如果其中一个为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/的评论中提供.我认为这是最优雅,最干净的解决方案.它适用于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);
Run Code Online (Sandbox Code Playgroud)

  • 如MySQL文档中所述,在单个语句中分配和读取变量是不安全的.操作顺序无法保证.所以唯一安全的方法是#4 (23认同)
  • @Jhawins那是因为beerpla.net是我的博客. (15认同)
  • 你知道,我从来没有想过这个愚蠢的面试问题有一个实际的用途,要求在不使用临时的情况下交换两个变量,但在这里它是,并且对于整数,这实际上会起作用:update swap_test set x = x + y, Y = XY,X = XY; (7认同)

Rol*_*DBA 47

您可以使用X和Y减去总和并减去相反的值

UPDATE swaptest SET X=X+Y,Y=X-Y,X=X-Y;
Run Code Online (Sandbox Code Playgroud)

这是一个示例测试(它与负数一起使用)

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>
Run Code Online (Sandbox Code Playgroud)

这是正在执行的交换

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>
Run Code Online (Sandbox Code Playgroud)

试试看 !!!

  • 对于数字来说,它确实是最好的. (4认同)
  • 如果添加时值溢出可能会出现问题? (2认同)

Dip*_*pin 23

以下代码适用于我的快速测试中的所有方案:

UPDATE table swap_test
   SET x=(@temp:=x), x = y, y = @temp
Run Code Online (Sandbox Code Playgroud)


Gre*_*ill 10

UPDATE表SET X = Y,Y = X将完全按照您的要求进行操作(编辑:在PostgreSQL中,而不是MySQL,见下文).这些值取自旧行并分配给同一行的新副本,然后替换旧行.您不必使用临时表,临时列或其他交换技巧.

@ D4V360:我明白了.这令人震惊和意外.我使用PostgreSQL,我的答案在那里正常工作(我试过).请参阅PostgreSQL UPDATE文档(在Parameters,expression下),其中提到SET子句右侧的表达式显式使用列的旧值.我看到相应的MySQL UPDATE文档包含语句"单表UPDATE赋值通常从左到右进行评估",这意味着您描述的行为.

很高兴知道.

  • 恕我直言,这个答案是一个混乱 - 糟糕的建议与"哎呀从不介意"附加.其中一半应该是评论,剩下的唯一与问题相关的部分是MySQL文档的链接...... (3认同)
  • 我用PostgreSQL和SET X = Y,Y = X救了我:) (2认同)

fij*_*ter 5

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;
Run Code Online (Sandbox Code Playgroud) 像这样的东西吗?

关于Greg的评论: 不,这不起作用:

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;
Run Code Online (Sandbox Code Playgroud)


mer*_*tio 5

好的,所以只是为了好玩,你可以做到这一点!(假设您正在交换字符串值)

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)
Run Code Online (Sandbox Code Playgroud)

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

或者,如果它们是数字,只需使用XOR.你提到了坐标,你有可爱的整数值,还是复杂的字符串?

编辑:顺便说一句,XOR的东西是这样的:

update swapper set foo = foo ^ bar, bar = foo ^ bar, foo = foo ^ bar;
Run Code Online (Sandbox Code Playgroud)


wor*_*cle 5

我相信有一个中间交换变量是这种方式的最佳实践:

update z set c1 = @c := c1, c1 = c2, c2 = @c
Run Code Online (Sandbox Code Playgroud)

首先,它始终有效;其次,无论数据类型如何,它都可以工作。

尽管两者

update z set c1 = c1 ^ c2, c2 = c1 ^ c2, c1 = c1 ^ c2
Run Code Online (Sandbox Code Playgroud)

update z set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2
Run Code Online (Sandbox Code Playgroud)

正常工作,顺便说一下,仅适用于数字数据类型,防止溢出是您的责任,您不能在有符号和无符号之间使用 XOR,也不能使用 sum 来解决溢出的可能性。

update z set c1 = c2, c2 = @c where @c := c1
Run Code Online (Sandbox Code Playgroud)

如果 c1 是 0 或 NULL 或零长度字符串或只是空格,则不起作用。

我们需要把它改成

update z set c1 = c2, c2 = @c where if((@c := c1), true, true)
Run Code Online (Sandbox Code Playgroud)

这是脚本:

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)
Run Code Online (Sandbox Code Playgroud)


pba*_*ney 5

正如其他答案所指出的,简单的交换不适用于 MySQL,因为它缓存了第 1 列的值 ,因为它在处理第 2 列之前立即,导致两列都被设置为第 2 列的值。

由于MySQL中不保证操作的顺序,所以使用临时变量也是不可靠的。

在不修改表结构的情况下交换两列的唯一安全方法是使用内部联接,这需要主键(id在本例中)。

UPDATE mytable t1, mytable t2
SET t1.column1 = t1.column2,
    t1.column2 = t2.column1
WHERE t1.id = t2.id;
Run Code Online (Sandbox Code Playgroud)

这将毫无问题地工作。