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 是相对高级更新技巧,它能让你的代码更高效、更简洁。

相关推荐
小谢小哥1 小时前
63-Gradle构建详解
java·后端·架构
代码中介商1 小时前
Redis位图实战:海量数据高效处理
数据库·redis·缓存
Sam_Deep_Thinking1 小时前
一个业务场景只需要一个ThreadLocal实例
java·面试
超梦dasgg1 小时前
Dijkstra(迪杰斯特拉)算法详解
java·数据结构·算法
MacroZheng1 小时前
给Claude Code装上这个超酷的状态栏,瞬间高大上了!
java·人工智能·后端
头歌实践平台1 小时前
头歌数据库 触发器
数据库
比企谷八幡1 小时前
数据库 Page 内部是什么样:Page Header、Slot 和 Line Pointer
数据库·c++·postgresql·数据库架构
代码地平线1 小时前
C++ 入门篇类和对象·上篇:从本质深剖类与对象与C++基本用法
c语言·开发语言·数据结构·c++·笔记·算法
有梦想的程序星空1 小时前
【环境配置】IDEA+Scala 项目 JAR 打包异常完整排查指南
java·ide·intellij-idea