Answers:
我只需要处理相同的问题,然后我将总结我的发现。
该UPDATE table SET X=Y, Y=X
方法显然行不通,因为它将两个值都设置为Y。
这是使用临时变量的方法。感谢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;
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;
我想出的另一种方法似乎可行:
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);
您可以取总和,然后使用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>
试试看 !!!
TINYINT
或来说非常有价值INT
,您是对的!
UPDATE表SET X = Y,Y = X会完全满足您的要求(编辑:在PostgreSQL中,不是MySQL,请参见下文)。这些值取自旧行,并分配给同一行的新副本,然后替换旧行。您不必诉诸使用临时表,临时列或其他交换技巧。
@ D4V360:我知道了。这是令人震惊和意外的。我使用PostgreSQL,我的答案在那儿可以正常工作(我尝试过)。请参阅PostgreSQL UPDATE文档(在Parameters(表达式)下),其中提到SET子句右侧的表达式显式使用列的旧值。我看到相应的MySQL UPDATE文档包含语句“通常从左到右评估单表UPDATE分配”,这表示您描述的行为。
很高兴知道。
好的,所以只是为了好玩,您可以这样做!(假设您要交换字符串值)
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。您提到了坐标,所以您有可爱的整数值还是复杂的字符串?
编辑:XOR东西的方式是这样的:
update swapper set foo = foo ^ bar, bar = foo ^ bar, foo = foo ^ bar;
我相信以这种方式,最好将中间变量作为交换变量:
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,也不能将sum用于溢出可能性。
和
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)
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;
像这样吗
编辑:关于格雷格的评论:不,这不起作用:
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)
此示例将start_date和end_date交换为日期错误的记录(当执行ETL进行重大重写时,我发现一些开始日期晚于结束日期)日期。糟糕的程序员!)。
在原位,出于性能原因,我正在使用MEDIUMINT(例如朱利安天,但其0根为1900-01-01),所以我可以在WHERE条件下执行mdu.start_date> mdu.end_date。
PK分别位于所有3列上(出于操作/索引的原因)。
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;
假设您想在tb_user中交换名字和姓氏的值。
最安全的是:
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
UPDATE table SET X = Y, Y = X
是在SQL中执行此操作的标准方法,只有MySQL行为不当。