149

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

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


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

4

23 回答 23

230

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

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

  2. 这是一种使用临时变量的方法。感谢来自http://beerpla.net/2009/02/17/swapping-column-values-in-mysql/评论的 Antony的“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);
于 2009-02-18T00:01:51.797 回答
57

您可以取总和并使用 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>

试试看 !!!

于 2012-07-31T21:44:28.007 回答
42

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

UPDATE swap_test
   SET x=(@temp:=x), x = y, y = @temp
于 2009-02-18T18:15:37.580 回答
12

UPDATE table SET X=Y, Y=X将精确地执行您想要的操作(编辑:在 PostgreSQL 中,而不是 MySQL,见下文)。这些值取自旧行并分配给同一行的新副本,然后替换旧行。您不必求助于使用临时表、临时列或其他交换技巧。

@D4V360:我明白了。这是令人震惊和意想不到的。我使用 PostgreSQL,我的答案在那里正常工作(我试过了)。请参阅PostgreSQL UPDATE 文档(在参数、表达式下),其中提到 SET 子句右侧的表达式显式使用列的旧值。我看到相应的MySQL UPDATE 文档包含语句“单表更新分配通常从左到右评估”,这意味着您描述的行为。

很高兴知道。

于 2008-09-01T09:31:30.213 回答
6

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

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;
于 2008-09-01T11:00:32.573 回答
6

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

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)
于 2014-08-22T09:43:49.070 回答
4

两种选择 1. 使用临时表 2. 研究XOR 算法

于 2008-09-01T09:24:35.563 回答
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;
像这样的东西?

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

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)

于 2008-09-01T09:25:56.467 回答
2

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

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

以上将不起作用(ERROR 1064 (42000): You have an error in your SQL syntax)

于 2008-12-27T13:47:55.963 回答
2

在 SQL Server 中,您可以使用以下查询:

update swaptable 
set col1 = t2.col2,
col2 = t2.col1
from swaptable t2
where id = t2.id
于 2019-07-19T21:32:52.797 回答
1

假设您的列中有符号整数,您可能需要使用 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 是第一个查询的结果。

于 2008-12-22T07:21:27.013 回答
1

我没试过但是

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

可能会这样做。

标记

于 2008-12-22T07:36:36.077 回答
1

可以更改列名,但这更像是一种技巧。但要小心可能在这些列上的任何索引

于 2009-08-27T22:57:02.907 回答
1

表名是客户。 字段是 a 和 b,将值交换为 b;。

更新客户 SET a=(@temp:=a), a = b, b = @temp

我检查了这工作正常。

于 2019-01-03T02:40:19.813 回答
1

您可以申请以下查询,它对我来说非常完美。

Table name: studentname
only single column available: name


update studentnames 
set names = case names 
when "Tanu" then "dipan"
when "dipan" then "Tanu"
end;

or

update studentnames 
set names = case names 
when "Tanu" then "dipan"
else "Tanu"
end;
于 2020-07-12T12:20:53.673 回答
0

使用单个查询交换列值

更新 my_table 集 a=@tmp:=a, a=b, b=@tmp;

干杯...!

于 2014-06-10T14:42:37.070 回答
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;
于 2015-02-09T17:30:03.560 回答
0

我必须将值从一列移动到另一列(如归档)并重置原始列的值。
以下(上面接受的答案中的#3参考)对我有用。

Update MyTable set X= (@temp:= X), X = 0, Y = @temp WHERE ID= 999;
于 2016-06-24T07:01:29.580 回答
0

此示例将start_dateend_date交换为日期错误的记录(在将 ETL 执行到主要重写时,我发现一些开始日期晚于它们的结束日期。下来,糟糕的程序员!)。

在原地,我出于性能原因使用 MEDIUMINTs(比如朱利安天,但根为 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;
于 2018-09-06T09:33:10.923 回答
0

假设您想交换 tb_user 中名字和姓氏的值。

最安全的是:

  1. 复制 tb_user。所以你将有 2 个表:tb_user 和 tb_user_copy
  2. 使用 UPDATE INNER JOIN 查询
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
于 2019-07-25T02:59:46.427 回答
0

如果要将 x 为 y 和 y 的所有列交换为 x;使用这个查询。

UPDATE table_name SET column_name = CASE column_name WHERE 'value of col is x' THEN 'swap it to y' ELSE 'swap it to x' END;

于 2020-08-23T14:47:52.420 回答
0

让我们想象一下这个表,让我们尝试交换“sex”表中的 m 和 f:

ID 姓名 性别 薪水
1 一种 2500
2 F 1500
3 C 5500
4 D F 500
UPDATE sex
SET sex = CASE sex
WHEN 'm' THEN 'f'
ELSE 'm'
END;

所以更新后的表变成:

ID 姓名 性别 薪水
1 一种 F 2500
2 1500
3 C F 5500
4 D 500
于 2020-09-29T22:14:14.567 回答
0

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

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

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

UPDATE table1 t1, table2 t2
SET t1.column1 = t1.column2,
    t1.column2 = t2.column1
WHERE s1.id = s2.id;

这将毫无问题地工作。

于 2022-03-04T17:42:00.570 回答