本文主要记录一次update……select
操作,适用于sql
操作小白。
应用场景:由于甲方需求变更,数据库表里增加了一个统计字段或者统计字段的计算逻辑发生了变化,导致统计字段的值需要更新或者重新计算。
能想到的解决方法以下几种:
- 通过
Java
代码在业务里面修改数据- 通过直接操作数据库表的方式修改数据
第一种方式可以采用selectById
和updateById
这种方式进行循环更新,也可以进行批量更新,总之实现的方式比较多且比较简单。
但这里采用的是第二种方式进行数据更新。
前置条件
假设有一个课程表,里面记录了a、b、c、d
这四门课的成绩,以及总成绩(需求版本1:总成绩等于各科成绩之和)。其中建表语句
# 建表语句
create table student_course
(
student_id int auto_increment primary key,
course_a int null,
course_b int null,
course_c int null,
course_d int null,
total_score int null
)
collate = utf8mb4_general_ci;
插入如下的测试数据:
INSERT INTO demo_data.student_course (student_id, course_a, course_b, course_c, course_d, total_score) VALUES (1, 80, 90, 70, 90, 330);
INSERT INTO demo_data.student_course (student_id, course_a, course_b, course_c, course_d, total_score) VALUES (2, 60, 60, 60, 80, 260);
在上述的基础数据之上,甲方爸爸突然告诉你计算总成绩的方式要变成couse_a * 0.5 + course_b * 0.5 + course_c + course_d
的方式,且以前入库的历史数据也要变成这样。为了完成这个需求聪明的你一定想到了先修改java
代码在重新上传之前的历史数据,但是如果之前的源文件丢失了那么就是一个悲伤的事情了,虽然也可以通过其他方式解决这个问题。为了能够早点完成这个需求,用更多的时间去学习摸鱼,这里我们选择最简单粗暴的方式,直接进行数据库层面的操作,操作之前记得先备份数据,否则可就出大问题了。
单行记录的更新
首先单条数据的单个字段更新,语法如下:
update tableName ... inner join (selete ... from tableName) on 关联条件 where 过滤条件。
按照上面的语法编写sql
语句,
update student_course t1 inner join (select student_course.student_id,
sum(student_course.course_a * 0.5 + student_course.course_b * 0.5
+ student_course.course_c + student_course.course_d) as totalA
from student_course
group by student_course.student_id) as t2
on t1.student_id = t2.student_id
set t1.total_score = t2.totalA
where t1.student_id = 1
运行这段sql
语句可得到如下输出,可以看到运行结果符合我们的预期。
于是乎我们兴致勃勃的将sql
修改为如下:
update student_course t1 inner join (select student_course.student_id,
sum(student_course.course_a * 0.5 + student_course.course_b * 0.5
+ student_course.course_c + student_course.course_d) as totalA
from student_course
group by student_course.student_id) as t2
on t1.student_id = t2.student_id
set t1.total_score = t2.totalA
where t1.student_id in (select t1.student_id from student_course);
手起刀落,bia🐔
一运行于是乎就得到了如下的运行结果:
可以看到并没有达到预期的效果。MySQL
果断的拒绝了我们并抛出一句错误信息You can't specify target table 'xxx' for update in FROM clause
。翻译过来大概就是:无法在FROM子句中指定更新的目标表。
批量更新
由于mysql
拒绝了我们,但是甲方又把刀架在脖子上让我们修改,因此时间、任务重我们就必须让mysql
同意我们的更新语句,但是解决方法是如何的呢?其解决方法就是给from
后面的sql
子句中多穿一层衣服()
即可,就是这么简单!因此修改sql
语句中的in
部分如下所示:
where t1.student_id in (select student_id from (select student_id from student_course) as t3);
此时运sql
语句我们就可以得到如下输出:
可以看到我们得到预期的结果,需求勉强实现。但是这里还需要注意下面两个坑:
- 多个字段同时更新的问题要用逗号隔开,不能用and关键字否则将达不到预期的效果。
update student_course set course_a = 90 and course_b = 100 where student_id = 1
这里的预期结果是 course_a
更新为 90
、course_b
更新为100
,实际上只是将course_a
更新为了0
,这里的原因是mysql
错误的(误解了我们的要表达的意思)将90 and course_b = 100
当作运算条件运算了即原来的course_b = 90 != 100
其运算结果为false
,而mysql
的0
表示为false
且 0 != 90
所以就将course_a
的值更新为了0
。
所以为了避免错误要将and变为逗号;
update student_course set course_a = 90 , course_b = 100 where student_id = 1
- 第二个坑主要就是因为自己太菜了,在运行
sql
逻辑的时候,由于实际的业务都计算逻辑并没有这么简单,导致在某些数据上实际的统计结果和之前的计算方式的计算结果是一样的,导致mysql
一直输出affect 0 row
,结果被我误以为sql
不正确。实际上是起了作用的。真是一个悲伤的故事!
参考资料:
- bugs.mysql.com/bug.php?id=…
- MySql - Update table using select statment from same table
- MySQL - UPDATE query based on SELECT Query
- MySQL Error 1093 - Can't specify target table for update in FROM clause
- Server SQL Modes