You need to enable JavaScript to run this app. TypeORM执行种子数据迁移时报错:relation "account" does not exist
优惠活动
大模型
产品
解决方案
定价
更多

TypeORM执行种子数据迁移时报错:relation "account" does not exist

数据库迁移执行种子数据时提示表不存在的问题排查

基本信息

  • 报错内容:执行种子数据迁移脚本时抛出QueryFailedError: relation "account" does not exist
  • 已知前置条件:account、source表的创建逻辑均写在其他独立迁移文件中
  • 运行环境版本:
    • PostgreSQL 12.6
    • Node 14.0.0
    • TypeORM 0.2.32
    • @nestjs/typeorm 7.1.5

关联代码

账户实体 account.entity.ts

import { BeforeInsert, Column, Entity, OneToMany } from 'typeorm';
import { AbstractEntity } from '../../common/abstract.entity';
import { SourceEntity } from '../source/source.entity';
import { UtilsService } from '../../shared/services/utils.service';

@Entity({ name: 'account' })
export class AccountEntity extends AbstractEntity {
  @Column({ unique: true })
  username: string;

  @Column({ nullable: true })
  password: string;

  @OneToMany(() => SourceEntity, (source) => source.account, {
    cascade: true,
  })
  sources: SourceEntity[];

  @BeforeInsert()
  async setPassword() {
    this.password = UtilsService.generateHash(this.password);
  }
}

资源实体 source.entity.ts

import { Column, Entity, ManyToOne } from 'typeorm';
import { AbstractEntity } from '../../common/abstract.entity';
import { AccountEntity } from '../account/account.entity';

@Entity({ name: 'source' })
export class SourceEntity extends AbstractEntity {
  @Column({ unique: true })
  username: string;

  @Column({ default: true })
  overrideCaption: boolean;

  @ManyToOne(() => AccountEntity, (account) => account.sources)
  account: AccountEntity;
}

种子数据迁移文件 seed-data.migration.ts

import { getCustomRepository, MigrationInterface, QueryRunner } from 'typeorm';
import { AccountRepository } from '../modules/account/account.repository';
import { SourceRepository } from '../modules/source/source.repository';

type DataType = {
  username: string;
  password: string;
  sources: { username: string }[];
};

export class SeedData1617243952346 implements MigrationInterface {
  private data: DataType[] = [
    {
      username: 'test',
      password: 'password',
      sources: [
        { username: 'some_test' },
        { username: 'okey_test' },
        { username: 'super_test' },
      ],
    },
    {
      username: 'account',
      password: 'password',
      sources: [
        { username: 'some_account' },
        { username: 'okey_account' },
        { username: 'super_account' },
      ],
    },
  ];

  public async up(): Promise<void> {
    await Promise.all(
      this.data.map(async (item) => {
        const accountRepository = getCustomRepository(AccountRepository);
        const accountEntity = accountRepository.create();
        accountEntity.username = item.username;
        accountEntity.password = item.password;

        const sourceRepository = getCustomRepository(SourceRepository);
        const sources = [];

        await Promise.all(
          item.sources.map(async (sourceItem) => {
            const sourceEntity = sourceRepository.create();
            sourceEntity.username = sourceItem.username;

            sources.push(sourceEntity);
          }),
        );

        accountEntity.sources = sources;
        const account = await accountRepository.save(accountEntity);
        console.log('Account created:', account.id);
      }),
    );
  }

  public async down(): Promise<void> {
    await Promise.all(
      this.data.map(async (item) => {
        const sourceRepository = getCustomRepository(SourceRepository);
        const accountRepository = getCustomRepository(AccountRepository);

        const account = await accountRepository.findOne({
          where: { username: item.username },
        });
        if (account) {
          await Promise.all(
            item.sources.map(async (src) => {
              const source = await sourceRepository.findOne({
                where: { username: src.username },
              });
              if (source) {
                await sourceRepository.delete(source);
              }
            }),
          );

          await accountRepository.delete(account);
        }
      }),
    );
  }
}

完整报错日志

Error during migration run:
QueryFailedError: relation "account" does not exist
    at new QueryFailedError (/home/wiha/dev/own/instahub/src/error/QueryFailedError.ts:9:9)
    at PostgresQueryRunner.<anonymous> (/home/wiha/dev/own/instahub/src/driver/postgres/PostgresQueryRunner.ts:228:19)
    at step (/home/wiha/dev/own/instahub/node_modules/tslib/tslib.js:143:27)
    at Object.throw (/home/wiha/dev/own/instahub/node_modules/tslib/tslib.js:124:57)
    at rejected (/home/wiha/dev/own/instahub/node_modules/tslib/tslib.js:115:69)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  length: 106,
  severity: 'ERROR',
  code: '42P01',
  detail: undefined,
  hint: undefined,
  position: '13',
  internalPosition: undefined,
  internalQuery: undefined,
  where: undefined,
  schema: undefined,
  table: undefined,
  column: undefined,
  dataType: undefined,
  constraint: undefined,
  file: 'parse_relation.c',
  line: '1191',
  routine: 'parserOpenTable',
  query: 'INSERT INTO "account"("id", "created_at", "updated_at", "username", "password") VALUES (DEFAULT, DEFAULT, DEFAULT, $1, $2) RETURNING "id", "created_at", "updated_at"',
  parameters: [
    'test',
    '$2b$10$iB6yb3D8e6iGmKoVAJ7eYeYfoItclw5lcVXqauPf9VH94DlDrbuSa'
  ]
}

问题定位与修复方案

问题根因:

  1. 迁移执行顺序错位:TypeORM 严格按照迁移类名中的时间戳从小到大执行脚本。如果种子迁移文件的时间戳早于建表迁移文件的时间戳,会出现尚未建表就尝试写入数据的逻辑错误。
  2. 迁移上下文不匹配:当前迁移代码中通过getCustomRepository()获取的仓库实例来自全局数据库连接,不会绑定到当前迁移专用的QueryRunner会话,即便表结构已经创建,也可能因会话隔离、事务可见性问题出现表无法识别的报错。

修复步骤:

  • 核对所有迁移文件的时间戳,保证所有建表迁移的执行时机早于种子数据迁移。
  • 改写迁移方法,通过注入的QueryRunner获取实体仓库,替换全局getCustomRepository调用,示例改写参考:
// 注意:up/down 方法需要接收 queryRunner 参数,不能使用无参写法
public async up(queryRunner: QueryRunner): Promise<void> {
  const accountRepository = queryRunner.manager.getRepository(AccountEntity);
  const sourceRepository = queryRunner.manager.getRepository(SourceEntity);
  // 原有业务逻辑代码不变,仅替换仓库实例的获取方式即可
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.27 17:15:03