SQL内功笔记 · 第9篇:UPDATE FROM 进阶——告别逐行子查询,拥抱集合更新

前言

为什么你需要 UPDATE FROM

在日常开发中,我们经常会遇到这样的场景:需要根据另一张表的数据来更新当前表的字段。新手通常的第一反应是写一个子查询:

复制代码
UPDATE user_info 
SET dept_name = (SELECT dept_name FROM dept WHERE id = user_info.dept_id);

这个写法虽然能工作,但存在两个致命问题:

  • 性能灾难:子查询会为每一行都执行一次,当数据量达到万级、十万级时,更新速度会急剧下降

  • 功能局限:当需要根据聚合结果(如 SUM、COUNT)来更新时,子查询写法会变得非常臃肿甚至难以实现

UPDATE FROM 语法正是为了解决这两个痛点而生的。它采用集合操作的思路:先通过 JOIN 将目标表和来源表关联成一张临时结果集,然后一次性完成更新。无论是一对一的关联更新,还是需要先 GROUP BY 聚合再更新,UPDATE FROM 都能以清晰、高效的 SQL 完成。

PostgreSQL、SQL Server 等数据库原生支持这种语法,而 MySQL 用户则需要用多表更新的方式来实现类似效果。这不是固定的语法,而是一种通用的解决思路。


语法差异

📌 语法差异说明

PostgreSQL / SQL Server / SQLite 使用 UPDATE ... FROM ... 语法法

MySQL 使用 UPDATE ... JOIN ... SET ... 语法

本文以 PostgreSQL 语法为主,MySQL 用户可将 FROM 子句改写为 JOIN,将 WHERE 关联条件移到 ON 中。。


UPDATE FROM

基础UPDATE语法:

sql 复制代码
-- update sql结构
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

UPDATE FROM 用法:

sql 复制代码
UPDATE 目标表 t                     
SET    字段 = 来源表.字段
FROM   来源表
WHERE  t.id = 来源表.id;

PS: UPDATE的主表永远是目标表 t。


demo的表结构

sql 复制代码
create table user_info(
    id bigint,
    name varchar(50),
    dept_id bigint,
    dept_name varchar(50)
);
create table dept(
    id bigint,
    dept_name varchar(50)
);

关联更新

sql 复制代码
update user_info u             
set dept_name = d.dept_name
from dept d 
where u.dept_id = d.id  

执行逻辑 :

  • 先查询 select * from user_info u join dept d on u.dept_id = d.id; 得到结果
  • 再把 d.dept_name 更新到 u.dept_name

子查询聚合后更新

sql 复制代码
update sale_order s
set total_amount = t.amount
from (
    select order_id,
           sum(qty * price) as amount
    from order_item
    group by order_id
) t
where s.id = t.order_id;

使用场景:

  • 更新库存余额
  • 更新总金额
  • 更新已发数量
  • 更新已入库数量
  • 订单明细sale_order主表;order_item

多字段同时更新

sql 复制代码
update sale_order s                -- 常见用法
set total_qty    = t.qty,
    total_amount = t.amount,
    item_count   = t.cnt
from (
    select order_id,
           sum(qty) as qty,
           sum(amount) as amount,
           count(*) as cnt
    from order_item
    group by order_id
) t
where s.id = t.order_id;

使用 WITH + UPDATE FROM

sql 复制代码
with tmp as (         -- 进阶用法
    select order_id,
           sum(amount) as total_amount
    from order_item
    group by order_id
)
update sale_order s
set total_amount = t.total_amount
from tmp t
where s.id = t.order_id;

递归结果更新

sql 复制代码
with recursive tree as (
    select id,parent_id,1 as level
    from dept
    where parent_id is null

    union all

    select d.id,d.parent_id,t.level + 1
    from dept d
    join tree t on d.parent_id = t.id
)
update dept d
set dept_level = t.level
from tree t
where d.id = t.id;

批量 CASE WHEN 更新 vs UPDATE FROM

sql 复制代码
update test
set status =
case
    when id = 1 then 'A'
    when id = 2 then 'B'
end;
update test t            -- 推荐下面这种写法,适用于 Java 批量更新、导入 Excel 或临时修正数据。据
set status = x.status
from (
    values
        (1,'A'),
        (2,'B')
) as x(id,status)
where t.id = x.id;

UPDATE FROM + EXISTS

sql 复制代码
update stock s
set status = 'EMPTY'
from (
    select item_id
    from stock
    group by item_id
    having sum(qty) <= 0
) t
where s.item_id = t.item_id;
  • 避免重复 提高安全性

最危险的问题

多对一更新问题,

sql 复制代码
update a 
set name = b.name
from b            
where a.id = b.id;

-- 正确做法 from 后面的结果唯一    
-- group by
select id,max(name)
group by id

-- distinct on
select distinct on(id)
       id,name
from b
order by id,create_time desc

总结

场景 推荐写法 核心要点
普通关联更新 UPDATE ... FROM ... WHERE 确保关联键唯一,避免多对一覆盖
聚合统计更新 子查询 GROUP BY + UPDATE FROM 企业系统中最常见的模式
多字段同时更新 子查询返回多个聚合字段 一次扫描,多处更新
复杂逻辑更新 WITH CTE + UPDATE FROM 逻辑清晰,易于维护
批量定点更新 VALUES() 构造临时表 Java 批量导入、Excel 修正数据的最佳实践
安全检查 EXISTS 或提前去重 防止误更新,提高健壮性

注意事项

⚠️关联键的唯一性 :当 FROM 子句中的来源表有多条记录匹配目标表的一条记录时,最终更新结果是不确定的(取决于最后匹配到的哪一行)。务必使用 GROUP BYDISTINCT ON 或窗口函数确保来源表的关联键唯一。

适用场景

  • 数据同步:从维度表更新事实表的冗余字段
  • 统计回写:订单汇总金额写回主表、库存余额汇总
  • 批量修正:Excel 导入临时数据批量更新
  • 递归更新:组织架构层级、BOM 成本分摊

UPDATE FROM 是相对高级更新技巧,它能让你的代码更高效、更简洁。

相关推荐
小bo波43 分钟前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
SamDeepThinking1 小时前
高并发场景下,CompletableFuture与ForkJoinPool该如何取舍?
java·后端·面试
张不才4 小时前
CPU 100% 了怎么办?Java 性能排障的标准化操作
java·后端
李白客5 小时前
KES新版MySQL兼容能力再升级意味着什么?
mysql·国产数据库
shepherd1116 小时前
吞吐量提升 10 倍:高并发大批量数据处理任务的架构演进与性能调优
java·后端·架构
ClouGence8 小时前
Oracle 数据同步为什么会出现数据不一致?长事务是常被忽略的原因
数据库·后端·oracle
plainGeekDev8 小时前
单例模式 → object 声明
android·java·kotlin
用户298698530149 小时前
Java 实现 Word 文档文本与图片提取的方法
java·后端
飞将10 小时前
从零实现数据库(2)——HashIndex + IndexManager
数据库