You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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中完成初始化即可使用:

  1. 安装依赖
npm install sequelize-hierarchy
  1. 在Model中初始化层级配置
    修改AccHead Model,添加静态初始化方法:
@Table()
export class AccHead extends Model {
  // ... 原有字段定义

  // 初始化hierarchy配置
  static initHierarchy() {
    this.isHierarchy({ foreignKey: 'parentId' }); // 指定自关联外键
  }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 00:24:56