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

如何在NestJS中使用sequelize-typescript递归获取所有子节点

在NestJS中用sequelize-typescript递归获取全层级分类

问题根源

你遇到的栈溢出是因为默认作用域中递归引用了自身的关联,导致Sequelize无限循环解析关联配置;而nested: true仅能获取2层,是因为Sequelize默认的嵌套深度限制。下面提供三种可行的解决方案,按需选择:


方案1:手动递归查询(小数据量首选)

直接通过代码递归查询每一层子分类,逻辑简单易懂,适合分类层级不多、数据量较小的场景。

代码实现

  1. 分类模型(确保已定义父子关联):
// category.model.ts
import { Table, Column, Model, DataType, HasMany, BelongsTo } from 'sequelize-typescript';

@Table({ tableName: 'categories' })
export class Category extends Model {
  @Column({ type: DataType.INTEGER, primaryKey: true, autoIncrement: true })
  id: number;

  @Column({ type: DataType.STRING, allowNull: false })
  name: string;

  @Column({ type: DataType.INTEGER, allowNull: true })
  parentId: number;

  @BelongsTo(() => Category, 'parentId')
  parent: Category;

  @HasMany(() => Category, 'parentId')
  children: Category[];
}
  1. Service层递归方法:
// category.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Category } from './category.model';

@Injectable()
export class CategoryService {
  constructor(@InjectModel(Category) private categoryModel: typeof Category) {}

  async getFullChildCategories(parentId: number): Promise<Category[]> {
    // 查询当前父ID下的直接子分类
    const children = await this.categoryModel.findAll({
      where: { parentId },
      attributes: ['id', 'name', 'parentId'], // 按需选择字段,减少数据传输
    });

    // 递归遍历每个子分类,查询其子分类
    for (const child of children) {
      child['children'] = await this.getFullChildCategories(child.id);
    }

    return children;
  }
}

方案2:Sequelize动态递归Include(中等数据量)

通过手动构建递归的Include配置,替代默认作用域的关联,避免循环引用导致的栈溢出,同时可自定义递归深度。

代码实现

在Service层编写递归Include生成函数,然后用于查询:

// category.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Category } from './category.model';

@Injectable()
export class CategoryService {
  constructor(@InjectModel(Category) private categoryModel: typeof Category) {}

  // 生成指定深度的递归Include配置
  private buildRecursiveInclude(depth: number = 10): any {
    if (depth <= 0) return [];
    return {
      model: Category,
      as: 'children',
      required: false, // 允许无子分类的节点存在
      include: [this.buildRecursiveInclude(depth - 1)],
    };
  }

  async getCategoriesWithAllChildren(parentId: number) {
    return this.categoryModel.findAll({
      where: { parentId },
      include: [this.buildRecursiveInclude()], // 默认递归10层,可按需调整
    });
  }
}

注意事项

  • 避免在默认作用域中配置递归关联,否则仍会触发循环引用栈溢出。
  • 可根据业务实际情况调整depth参数,防止不必要的深层查询。

方案3:数据库递归查询(大数据量推荐)

利用数据库的递归CTE(Common Table Expression)特性,在数据库层面一次性查询所有层级数据,性能最优,适合分类层级深、数据量大的场景(需PostgreSQL 9.4+ / MySQL 8.0+)。

代码实现

// category.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Category } from './category.model';

@Injectable()
export class CategoryService {
  constructor(@InjectModel(Category) private categoryModel: typeof Category) {}

  async getRecursiveCategories(parentId: number): Promise<any[]> {
    // 递归CTE查询语句
    const query = `
      WITH RECURSIVE category_tree AS (
        SELECT id, name, parentId, 1 AS level
        FROM categories
        WHERE parentId = :parentId
        UNION ALL
        SELECT c.id, c.name, c.parentId, ct.level + 1
        FROM categories c
        JOIN category_tree ct ON c.parentId = ct.id
      )
      SELECT * FROM category_tree ORDER BY level, id;
    `;

    // 执行原生查询
    const [flatResults] = await this.categoryModel.sequelize.query(query, {
      replacements: { parentId },
      type: this.categoryModel.sequelize.QueryTypes.SELECT,
    });

    // 将扁平结果转换为树形结构
    return this.convertFlatToTree(flatResults);
  }

  // 辅助函数:扁平数组转树形结构
  private convertFlatToTree(categories: any[]): any[] {
    const categoryMap = new Map();
    const rootCategories: any[] = [];

    // 先将所有分类存入Map,便于快速查找
    categories.forEach(cat => {
      categoryMap.set(cat.id, { ...cat, children: [] });
    });

    // 构建树形关系
    categories.forEach(cat => {
      const parent = categoryMap.get(cat.parentId);
      if (parent) {
        parent.children.push(categoryMap.get(cat.id));
      } else {
        rootCategories.push(categoryMap.get(cat.id));
      }
    });

    return rootCategories;
  }
}

内容的提问来源于stack exchange,提问作者Saad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 22:10:30