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

文章目录

环境

  • 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值,有范围溢出的风险

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

相关推荐
梦想的旅途22 小时前
企业微信引用消息的实现与配置
数据库
是桃萌萌鸭~2 小时前
oracle中的 CDB 和 PDB 详解
数据库·oracle
❀͜͡傀儡师2 小时前
docker部署开源的SQL审核平台Yearning
sql·docker·开源
woniu_buhui_fei2 小时前
MySQL知识整理一
数据库·mysql
hopsky2 小时前
Kingbase SQL 解析方案
数据库·sql
Elastic 中国社区官方博客2 小时前
Elasticsearch:shell 工具不是上下文工程的银弹
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
数据库幼崽2 小时前
ProxySQL官方文档之Architecture Overview
mysql
Mike117.2 小时前
做 GBase 8c 迁移适配时,我更先盯兼容模式、对象改造和 SQL 行为差异,而不是急着把数据先搬过去
数据库
云计算老刘2 小时前
MySQL 服务器
mysql