在MySQL中交换列值


127

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

这可能以某种方式与UPDATE有关吗?更新表SET X = Y,Y = X显然不会满足我的要求。


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


5
请注意,这UPDATE table SET X = Y, Y = X是在SQL中执行此操作的标准方法,只有MySQL行为不当。
Antti Haapala'9

Answers:


204

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

  1. UPDATE table SET X=Y, Y=X方法显然行不通,因为它将两个值都设置为Y。

  2. 这是使用临时变量的方法。感谢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);

25
如MySQL文档所述,在单个语句中分配和读取变量并不安全。不能保证操作顺序。因此,唯一安全的方法是#4
AMIB,2013年

选项4为我工作。如果只需要将列交换为仅一些行,则显然可以向where子句添加更多条件。
布拉德·坎贝尔

7
您知道,我从没想过这个愚蠢的面试问题要求在不使用临时变量的情况下交换两个变量,但实际上是可行的,对于整数,这实际上是可行的:update swap_test set x = x + y, y = xy,x = xy;
izak

此答案大部分是直接复制/粘贴自Beerpla.net/2009/02/17/swapping-column-values-in-mysql

17
@Jhawins那是因为beerpla.net是我的博客。
Artem Russakovskii 2015年

52

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

试试看 !!!


5
对于数字来说,确实是最整洁的数字。
常识

如果添加时值溢出可能会成为问题?
ToolmakerSteve

@ToolmakerSteve也许对于TINYINT或来说非常有价值INT,您是对的!
RolandoMySQLDBA

29

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

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

UPDATE table swap_test?不是UPDATE swap_test吗?

12

UPDATE表SET X = Y,Y = X会完全满足您的要求(编辑:在PostgreSQL中,不是MySQL,请参见下文)。这些值取自旧行,并分配给同一行的新副本,然后替换旧行。您不必诉诸使用临时表,临时列或其他交换技巧。

@ D4V360:我知道了。这是令人震惊和意外的。我使用PostgreSQL,我的答案在那儿可以正常工作(我尝试过)。请参阅PostgreSQL UPDATE文档(在Parameters(表达式)下),其中提到SET子句右侧的表达式显式使用列的旧值。我看到相应的MySQL UPDATE文档包含语句“通常从左到右评估单表UPDATE分配”,这表示您描述的行为。

很高兴知道。


感谢Greg和D4V360,很高兴知道PostgreSQL和MySQL在更新查询行为方面的差异。
Vijay Dev

就其价值而言,“ x = y,y = x”方法在Oracle中也适用。
Burhan Ali

2
我使用PostgreSQL,SET X = Y,Y = X救了我:)
匿名

4
恕我直言,这个答案是一团糟-不好的建议,加上“哎呀,没关系”。它的一半应该是一个注释和自己相关的问题剩下的唯一的部分是链接到MySQL的文件...
航空

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;

5

我相信以这种方式,最好将中间变量作为交换变量:

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)

+1可以最终为愚蠢的面试问题找到一个好用处,在这种情况下,您必须临时交换两个变量;-)
izak


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)


仅作记录:在PostgreSQL 有效,而在MySQL 中无效
海峡

2

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

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

上面的方法不起作用(错误1064(42000):您的SQL语法有错误)


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是第一个查询的结果。


1

我没有尝试过

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

可能会做。

标记


1

可以更改列名,但这更像是一个技巧。但请注意这些列上的任何索引


1

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

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

我检查这工作正常。


1

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

update swaptable 
set col1 = t2.col2,
col2 = t2.col1
from swaptable t2
where id = t2.id


0

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

Update MyTable set X= (@temp:= X), X = 0, Y = @temp WHERE ID= 999;

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

此示例将start_dateend_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;

仅供参考:此代码在0.203秒内更新了145 / 108,456条记录。这是一项一次性的任务,因此性能并不重要。
安德鲁·福斯特

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

0

您可以在下面的查询中应用它,它对我来说非常理想。

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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.