把数据库表里两列的值互换

文章目录

环境

  • Windows 11 专业版
  • XAMPP v3.3.0
    • MySQL Ver 15.1
    • MySQL Workbench 8.0 CE

需求

t1 中有两列 c1c2

现在,要把 c1c2 的值交换一下。

错误方法

一开始想到的方法是在update语句里直接赋值:

sql 复制代码
update t1 set c1 = c2, c2 = c1 where id = 1;

但是,这种做法是行不通的。你会发现, c2 的值并没有更新:

究其原因,是因为update语句的更新顺序是"从左右到依次执行"的,而且使用的是"最新的值"。对于本例来说,第一步通过 c1 = c2c1 的值更新为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,这就解决了"把 c1c2 的值临时存放一下"的问题:

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值,则交换后 c1c2 都会变成null值(因为 c1 + c2 是null)
  • 要小心范围溢出

总结

方法 优点 缺点
直接交换 X 逻辑错误
临时变量(包含在同一SQL里) 短小,简单 已废弃
临时变量(独立SQL) 逻辑清晰、自然 需要两个SQL,且只能处理一条记录
自连接 不需要引入其它东西 性能上需要额外的join,逻辑上稍微有点绕,且join时必须确保一一对应(通过主键join)
加减法 巧妙,短小,简单 只能处理数值类型,无法正确处理null值,有范围溢出的风险

这么看,还是自连接比较好一些,这就要求表的设计要规范(有主键)。

相关推荐
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第87题】【Mysql篇】第17题:分布式事务的实现原理?
java·数据库·分布式·mysql·面试
yyuuuzz1 小时前
独立站的技术基础与常见运维问题
大数据·运维·服务器·网络·数据库·aws
键盘上的猫头鹰5 小时前
【MySQL 教程(八)】索引、事务、用户管理、导入导出与分页查询
数据库·python·mysql
Royzst5 小时前
数据库知识点
数据库
雪的季节5 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt
宋浮檀s5 小时前
应急响应——Web漏洞:命令执行+SSRF+弱口令
运维·数据库·sql·网络安全·oracle·应急响应
yurenpai(27届找实习中)7 小时前
redis_点评(21.好友关注——关注、取关功能实现;共同关注功能实现)
数据库·redis·缓存
Rick19937 小时前
索引的排序和分组
数据库·mysql
爱莉希雅&&&7 小时前
zabbix快速搭建和使用
android·linux·数据库·zabbix·监控
不爱编程的小陈7 小时前
事务的进化:从MySQL单机事务到TiDB分布式事务的探究
分布式·mysql·tidb