如何在TypeORM中实现带SELECT子查询的UPDATE SET更新操作
TypeORM 实现该更新逻辑的方法
你写的是PostgreSQL方言的更新语句,核心逻辑是关联派生表计算更新值、执行更新后返回全量记录,TypeORM里最贴合原逻辑的实现方式是用QueryBuilder直接构造对应语句,不需要额外封装。
推荐写法:QueryBuilder单语句实现(和原SQL逻辑完全一致,原子性有保障)
直接构造子查询关联更新,生成的SQL和你手写的原生语句几乎无差别,不会有额外开销:
// 引入你定义的Document实体,路径按项目实际情况替换 import { Document } from 'src/entities/document.entity'; async function updateTargetDocument() { const repository = dataSource.getRepository(Document); // 构造子查询:查指定分区下最大的documentNumber const maxNumberSubQuery = repository .createQueryBuilder('d') .select('d.documentNumber', 'maxNum') .where('d.documentAreaId = :areaId', { areaId: 1 }) .orderBy('d.documentNumber', 'DESC') .limit(1); // 构造主更新语句 const updateResult = await repository .createQueryBuilder() .update() .set({ documentNumber: () => `"a"."maxNum" + 1` }) .from(`(${maxNumberSubQuery.getQuery()})`, 'a') .setParameters(maxNumberSubQuery.getParameters()) .where('id = :targetId', { targetId: 29497 }) .returning('*') .execute(); // 返回更新后的完整记录 return updateResult.raw[0] as Document; }
注意事项
- 如果你没有在TypeORM连接配置里开启自动下划线转驼峰的命名策略,实体字段名和数据库字段名不一致的话,要手动给字段名加双引号匹配数据库命名,比如
"documentNumber" returning('*')是PostgreSQL、SQLite等少数数据库支持的语法,MySQL不支持该语法,如果你用的是MySQL就需要更新后额外查一次记录- 这种单语句写法是原子操作,高并发下不会出现取到重复编号的问题,适合单据号生成这类对唯一性要求高的场景,记得额外给
documentAreaId和documentNumber加联合唯一索引做兜底。
可选写法:两步查询实现(逻辑简单,适合无并发要求的场景)
如果你的场景并发量极低,也可以拆成两次查询写,代码更易读:
async function updateTargetDocumentTwoStep() { const repository = dataSource.getRepository(Document); // 第一步查最大编号 const maxDoc = await repository.findOne({ where: { documentAreaId: 1 }, order: { documentNumber: 'DESC' }, take: 1 }); const nextNumber = (maxDoc?.documentNumber ?? 0) + 1; // 第二步更新并返回 const updateResult = await repository .createQueryBuilder() .update() .set({ documentNumber: nextNumber }) .where('id = :id', { id: 29497 }) .returning('*') .execute(); return updateResult.raw[0] as Document; }
警告:两步写法不是原子操作,两个请求同时执行的时候,大概率会拿到相同的最大编号,生成重复的documentNumber,生产环境不推荐用在编号生成场景。
内容的提问来源于stack exchange,提问作者Ivelin
相关产品推荐
相关产品推荐

