文章目录
环境
- Windows 11 专业版
- XAMPP v3.3.0
- MySQL Ver 15.1
- MySQL Workbench 8.0 CE
需求
表 t1 中有两列 c1 、 c2 :

现在,要把 c1 和 c2 的值交换一下。
错误方法
一开始想到的方法是在update语句里直接赋值:
sql
update t1 set c1 = c2, c2 = c1 where id = 1;
但是,这种做法是行不通的。你会发现, c2 的值并没有更新:

究其原因,是因为update语句的更新顺序是"从左右到依次执行"的,而且使用的是"最新的值"。对于本例来说,第一步通过 c1 = c2 把 c1 的值更新为456是OK的,但是在第二步 c2 = c1 时, c1 此时的值已经是456了,因此 c2 的值并不会更新为123,而是会更新为456(没有变化)。
关于update的更新顺序,参见我另一篇文档: https://blog.csdn.net/duke_ding2/article/details/159437368 。
正确方法
有几种办法可以实现两列的值互换。
方法1:引入临时变量
"交换两个变量的值"是一个常见需求,以Java为例,实现如下:
java
var a = 123;
var b = 456;
var temp = a;
a = b;
b = temp;
System.out.println("a = " + a + ", b = " + b);
同理,我们可以在SQL里也采取这种做法:
sql
update t1 set c1 = (@temp := c1), c1 = c2, c2 = @temp
where id = 1;
实际运行效果如下:

其中, @temp 是MySQL的临时变量, @temp := c1 是对临时变量赋值,这是一个表达式,其值就是 @temp 被赋的值。
而 c1 = (@temp := c1) 可以看做是一个占位符,因为不能直接写成 set @temp := c1 ,所以通过"给 c1 赋其原值" 来满足SQL语法。
后面的部分就很容易理解了,只需把 c1 赋值为 c2 ,再把 c2 赋值为 @temp 即可。
不过,在有些MySQL版本中,这种做法会触发一个warning:
powershell
1 row(s) affected, 1 warning(s): 1287 Setting user variables within expressions is deprecated and will be removed in a future release. Consider alternatives: 'SET variable=expression, ...', or 'SELECT expression(s) INTO variables(s)'. Rows matched: 1 Changed: 1 Warnings: 1 0.000 sec
大意是说,"在表达式里设置用户变量"的做法已经废弃了。
替代方案是 SET variable=expression, ... 或者 SELECT expression(s) INTO variables(s) :
set:
sql
set @temp = (select c1 from t1 where id = 1);
update t1 set c1 = c2, c2 = @temp where id = 1;
实际运行效果如下:

select into:
sql
select c1 into @temp from t1 where id = 1;
update t1 set c1 = c2, c2 = @temp where id = 1;
实际运行效果如下:

不过这两种方法有点问题:它们都是在单独的SQL语句中给临时变量赋值的,因此临时变量只能有一个值。
方法2:自连接
顾名思义,自连接就是表 t1 和自身做join,这就解决了"把 c1 和 c2 的值临时存放一下"的问题:
sql
update t1 a
join t1 b on a.id = b.id
set a.c1 = b.c2, a.c2 = b.c1
where a.id = 1;
实际运行效果如下:

不过这种做法也有一定的限制,那就是join条件,一定要通过主键来做join,以确保每条记录和它的副本记录是一一对应的,否则会出问题。
方法3:巧妙利用加法和减法运算
先看一道常见面试题:已知两个整数a和b,不引入其它变量,把a和b的值互换。
以Java为例:
java
var a = 123;
var b = 456;
a = a + b; // 得到 sum
b = a - b; // 得到 a(sum - b)
a = a - b; // 得到 b(sum - a)
System.out.println("a = " + a + ", b = " + b);
原理比较简单,如果还看不太明白的话,实际单步调试一下,就会清楚了。
注意:要小心范围溢出,因为 a + b 有可能会越界溢出。
我们可以借鉴这种做法,在SQL里也这么做:
sql
update t1
set c1 = c1 + c2, c2 = c1 - c2, c1 = c1 - c2
where id = 1;
实际运行效果如下:

不过这种做法也有一定的限制:
- 要交换的列必须是数值类型
- null值和其它值运算的结果还是null,因此如果
c1或者c2是null值,则交换后c1和c2都会变成null值(因为c1+c2是null) - 要小心范围溢出
总结
| 方法 | 优点 | 缺点 |
|---|---|---|
| 直接交换 | X | 逻辑错误 |
| 临时变量(包含在同一SQL里) | 短小,简单 | 已废弃 |
| 临时变量(独立SQL) | 逻辑清晰、自然 | 需要两个SQL,且只能处理一条记录 |
| 自连接 | 不需要引入其它东西 | 性能上需要额外的join,逻辑上稍微有点绕,且join时必须确保一一对应(通过主键join) |
| 加减法 | 巧妙,短小,简单 | 只能处理数值类型,无法正确处理null值,有范围溢出的风险 |
这么看,还是自连接比较好一些,这就要求表的设计要规范(有主键)。