NestJS中如何使用sequelize-hierarchy实现无限层级自关联树形表?
解决NestJS+Sequelize+MSSQL无限层级树形表查询问题
需求说明
需要实现自关联树形表的无限层级嵌套JSON输出,表结构示例:
+----+--------------+--------+ | id | name | parent | +----+--------------+--------+ | 1 | ABC | null | | 2 | DEF | null | | 3 | GHI | 2 | | 4 | JKL | 3 | | 5 | MNO | 4 | +----+--------------+--------+
期望输出的嵌套JSON格式:
[ { "id": 1, "name": "ABC", "children": null }, { "id": 2, "name": "DEF", "children": [ { "id": 3, "name": "GHI", "children": [ { "id": 4, "name": "JKL", "children": [ { "id": 5, "name": "MNO", "children": null } ] } ] } ] } ]
现有Model定义
已通过NestJS装饰器定义好自关联Model:
@Table() export class AccHead extends Model { @Column({ type: DataType.BIGINT, primaryKey: true, autoIncrement: true, }) id: number; @Column name: string; @ForeignKey(() => AccHead) @Column({ type: DataType.BIGINT, onDelete: 'NO ACTION', }) parentId: number; @BelongsTo(() => AccHead, { foreignKey: 'parentId' }) parent: AccHead; @HasMany(() => AccHead, { foreignKey: 'parentId' }) children: AccHead[]; }
已尝试的局限方案
之前使用固定层级的include关联查询,仅支持有限层级嵌套,无法实现无限递归,且面对15万条数据时性能极差:
const hierarchicalData = await AccHead.findAll({ where: { parentId: null, // 仅查询顶层节点 }, include: [ { model: AccHead, as: 'children', where: { parentId: { [Op.ne]: null }, }, include: [ { model: AccHead, as: 'children', include: [ { model: AccHead, as: 'children', }, ], }, ], }, ], });
可行解决方案
方案一:在NestJS中集成sequelize-hierarchy
sequelize-hierarchy专门用于处理树形结构,只需在NestJS中完成初始化即可使用:
- 安装依赖
npm install sequelize-hierarchy
- 在Model中初始化层级配置
修改AccHeadModel,添加静态初始化方法:
@Table() export class AccHead extends Model { // ... 原有字段定义 // 初始化hierarchy配置 static initHierarchy() { this.isHierarchy({ foreignKey: 'parentId' }); // 指定自关联外键 } }
- 在Service中完成初始化并查询
在Service的onModuleInit钩子中初始化层级,之后即可使用封装好的层级查询方法:
@Injectable() export class AccHeadService implements OnModuleInit { constructor( @InjectModel(AccHead) private accHeadModel: typeof AccHead, ) {} async onModuleInit() { // 初始化hierarchy this.accHeadModel.initHierarchy(); } // 查询无限层级数据 async getHierarchicalData() { return this.accHeadModel.findAll({ where: { parentId: null }, hierarchy: true, // 启用层级查询 }); } }
方案二:CTE+内存构建树形结构(大数据量首选)
面对15万条数据,ORM递归查询易出现性能问题,推荐使用MSSQL的CTE(公共表表达式)一次性查询所有层级,再在内存中构建树形结构:
@Injectable() export class AccHeadService { constructor( @InjectModel(AccHead) private accHeadModel: typeof AccHead, ) {} async getHierarchicalData() { // 用CTE递归查询所有层级数据 const query = ` WITH RecursiveAccHeads AS ( SELECT id, name, parentId, 0 AS level FROM accHeads WHERE parentId IS NULL UNION ALL SELECT ah.id, ah.name, ah.parentId, rah.level + 1 FROM accHeads ah INNER JOIN RecursiveAccHeads rah ON ah.parentId = rah.id ) SELECT * FROM RecursiveAccHeads ORDER BY level, id; `; const [results] = await this.accHeadModel.sequelize.query(query); // 将扁平化结果转换为嵌套树形结构 return this.buildTree(results as AccHead[]); } // 内存中构建树形结构 private buildTree(nodes: AccHead[]): AccHead[] { const nodeMap = new Map<number, any>(); const rootNodes: any[] = []; // 初始化所有节点,创建children数组 nodes.forEach(node => { nodeMap.set(node.id, { ...node, children: [] }); }); // 将子节点挂载到父节点下 nodes.forEach(node => { if (node.parentId !== null) { const parent = nodeMap.get(node.parentId); if (parent) { parent.children.push(nodeMap.get(node.id)); } } else { rootNodes.push(nodeMap.get(node.id)); } }); // 空children转为null,匹配需求格式 rootNodes.forEach(node => this.setEmptyChildrenToNull(node)); return rootNodes; } private setEmptyChildrenToNull(node: any) { if (node.children.length === 0) { node.children = null; } else { node.children.forEach(child => this.setEmptyChildrenToNull(child)); } } }
方案对比:
- sequelize-hierarchy:封装完善,使用简单,但底层查询逻辑可能存在多次数据库请求,适合数据量较小的场景。
- CTE+内存构建:仅一次数据库查询,内存构建效率高,性能更优,适合15万条数据的大数据量场景。
内容的提问来源于stack exchange,提问作者Hashim Khan
相关产品推荐
相关产品推荐

