Skip to content

SQL 方案:QueryBuilder

用于构建复杂的 SQL 查询 / 操作,相比手写原生 SQL 更安全(防 SQL 注入)、更易维护,相比 TypeORM Repository 基础方法更灵活,能满足多条件、联表、聚合等复杂业务场景。

创建 QueryBuilder

两种方式:Repository 单实体、dataSource 跨实体

ts
// src/user/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>, // 注入 User 仓库
    private readonly dataSource: DataSource // 注入数据源
  ) {}

  // 方式一:从 Repository 创建,操作单个实体的查询 / 操作
  async 方式一() {
    return this.userRepository.createQueryBuilder('user');
  }

  // 方式二:从 DataSource 创建,跨实体、批量插入 / 更新 / 删除
  async 方式二() {
    return this.dataSource.createQueryBuilder();
  }
}

// 注意:createQueryBuilder('user') 中的 user 是实体的别名,后续所有操作(select/where/join)都基于该别名,需保证唯一(联表时避免冲突)。

QueryBuilder 常用 API

参考:https://typeorm.nodejs.cn/select-query-builder#what-is-querybuilder

  1. createQueryBuilder 参数
ts
createQueryBuilder(User); // 参数1 实体作为参数
createQueryBuilder('user'); // 参数1 实体名称字符串
createQueryBuilder(User, 'u'); // 参数1 实体作为参数,参数2 别名
createQueryBuilder('user', 'u', secondaryConnection); // 参数1 实体作为参数,参数2 别名,参数3 指定使用的数据库连接

// 等价于
createQueryBuilder().from('user', 'u');
  1. 查询相关 select() addSelect() from distinct()
ts
createQueryBuilder().select(['user.id', 'user.name']); // 指定查询的列,参数1 字符串或者字符串数组
createQueryBuilder().select('user'); // 返回整个实体

createQueryBuilder().addSelect('user.email'); // 添加额外的选择列,参数1 字符串或者字符串数组

createQueryBuilder().from(User, 'user'); // 设置主查询表,参数1 实体类或表名,参数2 表别名

createQueryBuilder().distinct(true); // 添加 DISTINCT 关键字,去重的作用

createQueryBuilder().setParameter('age', 18); // 设置主查询参数,参数1 key,参数2 value
createQueryBuilder().setParameters({ age: 18, gender: 'male' }); // 批量设置主查询参数,参数对象
  1. 条件查询 where() andWhere() orWhere() having()
ts
createQueryBuilder().where('user.age > :age', { age: 18 }); // where 条件,参数1 条件字符串或函数或 Brackets 对象,参数2 参数对象
createQueryBuilder().andWhere('user.status = :status', { status: 'active' }); // where and,参数同 where
createQueryBuilder().orWhere('user.isAdmin = :isAdmin', { isAdmin: true }); // where or,参数同 where

createQueryBuilder().having('COUNT(post.id) > :minPosts', { minPosts: 5 }); // HAVING 条件,参数1 条件字符串,参数2 参数对象
createQueryBuilder().andHaving('COUNT(post.id) > :minPosts', { minPosts: 5 }); // HAVING and,参数同having
createQueryBuilder().orHaving('COUNT(post.id) > :minPosts', { minPosts: 5 }); // HAVING or,参数同having
  1. 关联查询 innerJoin() leftJoin() rightJoin(),innerJoinAndSelect() leftJoinAndSelect()
ts
createQueryBuilder().innerJoin('user.posts', 'post'); // 内接其他表,参数1 实体类或表名,参数2 别名,参数3 连接条件,参数4 参数对象
createQueryBuilder().leftJoin('user.posts', 'post'); // 左接其他表,参数1 实体类或表名,参数2 别名,参数3 连接条件,参数4 参数对象
createQueryBuilder().rightJoin('user.posts', 'post'); // 右接其他表,参数1 实体类或表名,参数2 别名,参数3 连接条件,参数4 参数对象
createQueryBuilder().innerJoinAndSelect('user.posts', 'post'); // 内接其他表,参数1 实体类或表名,参数2 别名,参数3 连接条件,参数4 参数对象
createQueryBuilder().leftJoinAndSelect('user.posts', 'post'); // 左接其他表,参数1 实体类或表名,参数2 别名,参数3 连接条件,参数4 参数对象
  1. 排序 分组 分页
ts
createQueryBuilder().orderBy('user.name', 'DESC'); // 排序,参数1 排序字段,参数2 升序ASC(默认)& 降序DESC,参数3 null 值的处理方式
createQueryBuilder().addOrderBy('user.name', 'DESC'); // 排序,参数1 排序字段,参数2 升序ASC(默认)& 降序DESC,参数3 null 值的处理方式

createQueryBuilder().groupBy('user.country'); // 添加分组,参数1 分组字段
createQueryBuilder().addGroupBy('user.country'); // 添加额外的分组,参数1 分组字段

// skip与limit作用相同,take与 offset 作用相同
createQueryBuilder().skip(10); // 跳过的记录数,参数1 记录数,示例:.skip((currentPage - 1) * pages),模仿第几页
createQueryBuilder().limit(10); // 跳过的记录数,参数1 记录数,示例:.skip((currentPage - 1) * pages),模仿第几页
createQueryBuilder().take(5); // 返回的记录数,参数1 记录数,
createQueryBuilder().offset(5); // 返回的记录数,参数1 记录数,
  1. 缓存 事务
ts
createQueryBuilder().cache(true, 60000); // 启用/禁用查询缓存,参数1 是否启用,参数2 缓存时间(毫秒)
createQueryBuilder().useTransaction(true); // 是否使用事务,参数1 是否使用
  1. 最终方法(无参)
ts
createQueryBuilder().getQuery(); // 获取生成的 SQL

createQueryBuilder().getOne(); // 查询返回单个实体
createQueryBuilder().getOneOrFail(); // 查询返回单个实体,如果没有匹配则抛出错误

createQueryBuilder().getMany(); // 查询返回实体列表
createQueryBuilder().getManyAndCount(); // 查询返回实体列表和统计数

createQueryBuilder().getRawOne(); // 查询返回单个实体(原始值)

createQueryBuilder().getRawMany(); // 查询返回实体列表(原始值)

createQueryBuilder().getCount(); // 查询返回计数

createQueryBuilder().execute(); // 执行原始查询(用于 INSERT/UPDATE/DELETE)

插入(Insert)

ts
// 插入&批量插入
await this.userRepository
  .createQueryBuilder()
  .insert()
  .into(User)
  .values([
    { firstName: 'Timber', lastName: 'Saw' },
    { firstName: 'Phantom', lastName: () => "CONCAT('S', 'A', 'W')" }, // 支持原始 sql
  ])
  .execute();

// 出现不可重复冲突时,不执行插入操作,也不执行修改操作
await this.userRepository
  .createQueryBuilder()
  .insert()
  .into(User)
  .values({
    firstName: 'Timber',
    lastName: 'Saw',
    externalId: 'abc123',
  })
  .orIgnore()
  .execute();

// 存在即更新,不存在则插入
await this.userRepository
  .createQueryBuilder()
  .insert()
  .into(User)
  .values({
    firstName: 'Timber',
    lastName: 'Saw',
    externalId: 'abc123',
  })
  .orUpdate(['firstName', 'lastName'], ['externalId'], {
    skipUpdateIfNoValuesChanged: true, // 如果值未更改则跳过数据更新,节省性能
  })
  .execute();

删除(Delete)

  1. 物理删除
ts
async deleteUser(id: number) {
  const result = await this.dataSource
    .createQueryBuilder()
    .delete()
    .from(User)
    .where('id = :id', { id })
    .execute();

  return result.affected;
}
  1. 软删除(配合 @DeleteDateColumn)
ts
async softDeleteUser(id: number) {
  return this.userRepository
    .createQueryBuilder('user')
    .softDelete() // 软删除(自动填充 deleteTime 字段)
    .where('id = :id', { id })
    .execute();
}

// 查询包含软删除的用户
async findDeletedUser(id: number) {
  return this.userRepository
    .createQueryBuilder('user')
    .withDeleted() // 包含软删除记录
    .where('id = :id', { id })
    .getOne();
}

更新(Update)

ts
async updateUser(id: number, user: Partial<User>) {
  const result = await this.dataSource
    .createQueryBuilder()
    .update(User)
    .set(user) // 要更新的字段
    .where('user.id = :id', { id }) // 更新条件
    // .andWhere('user.status = :status', { status: 1 }) // 多条件
    .execute();

  return result.affected; // 返回受影响的行数
}

查询(Select)

查询结果

ts
// 查询全部字段
this.userRepository.createQueryBuilder('user').getMany(); // 返回数组

// 查询指定字段
return this.userRepository
  .createQueryBuilder('user')
  .select(['user.id', 'user.name', 'user.age']) // 指定字段
  .getMany();

// 字段别名
async findUserWithAlias() {
  return this.userRepository
    .createQueryBuilder('user')
    .select('user.name as userName') // 字段别名
    .addSelect('user.age as userAge')
    .getRawMany(); // getRawMany() 用于获取别名字段(返回原始对象)
}

条件查询(Where)

  1. 基础查询
ts
async findUserByCondition() {
  return this.userRepository
    .createQueryBuilder('user')
    .where('user.age > :age AND user.status = :status', { age: 18, status: 1 }) // 命名参数(推荐)
    .getMany();
}
  1. 动态条件(andWhere/orWhere)
ts
async findUsers(query: { name?: string; age?: number; status?: number }) {
  const { name, age, status } = query;
  const queryBuilder = this.userRepository.createQueryBuilder('user');

  // 动态拼接条件(避免空条件导致 SQL 错误)
  if (name) {
    queryBuilder.andWhere('user.name LIKE :name', { name: `%${name}%` }); // 模糊查询
  }
  if (age) {
    queryBuilder.andWhere('user.age = :age', { age });
  }
  if (status !== undefined) {
    queryBuilder.andWhere('user.status = :status', { status });
  }

  return queryBuilder.getMany();
}
  1. 条件分组(括号优先级)
ts
async findUserByGroupCondition() {
  return this.userRepository
    .createQueryBuilder('user')
    .where('user.id = :id', { id: 1 })
    // 分组条件:(name = 张三 OR age = 20)
    .orWhere('(user.name = :name AND user.age = :age)', { name: '张三', age: 20 })
    .getMany();
}

排序(OrderBy)

ts
async findUsersWithSort() {
  return this.userRepository
    .createQueryBuilder('user')
    .orderBy('user.createTime', 'DESC') // 按创建时间降序
    .addOrderBy('user.age', 'ASC') // 多字段排序:再按年龄升序
    // 也可传入对象:.orderBy({ 'user.createTime': 'DESC', 'user.age': 'ASC' })
    .getMany();
}

分页(Skip/Take)

ts
async findUsersWithPagination(page: number = 1, pageSize: number = 10) {
  const queryBuilder = this.userRepository.createQueryBuilder('user');

  // 分页计算:skip(偏移量) = (页码-1)*页大小,take(页大小)
  queryBuilder.skip((page - 1) * pageSize).take(pageSize);

  // 获取列表 + 总数(常用分页返回格式)
  const [list, total] = await queryBuilder.getManyAndCount();

  return { list, total, page, pageSize };
}

联表查询

ts
// 语法
leftJoin('user.profile', 'profile') // 左联表(不查询关联字段)
leftJoinAndSelect('user.profile', 'profile') // 左联表 + 查询关联字段(常用)
innerJoin('user.profile', 'profile') // 内联表(仅返回关联数据存在的记录)

async findUserWithProfile(id: number) {
  return this.userRepository
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.profile', 'profile') // 左联 Profile 并查询字段
    .where('user.id = :id', { id })
    .getOne();
}

聚合查询

ts
// 统计用户总数
async countUsers() {
  return this.userRepository.createQueryBuilder('user').getCount();
}

// 统计总年龄、平均年龄
async getUserAgeStats() {
  const result = await this.userRepository
    .createQueryBuilder('user')
    .select('SUM(user.age)', 'totalAge') // 求和
    .addSelect('AVG(user.age)', 'avgAge') // 平均值
    .getRawOne(); // getRawOne() 获取单行聚合结果

  return {
    totalAge: Number(result.totalAge),
    avgAge: Number(result.avgAge).toFixed(2), // 保留两位小数
  };
}

子查询

ts
async findUserWithSubQuery() {
  // 1. 定义子查询:查询年龄 > 20 的用户 ID
  const subQuery = this.userRepository
    .createQueryBuilder('u')
    .select('u.id')
    .where('u.age > :age', { age: 20 });

  // 2. 主查询:IN 子查询结果
  return this.userRepository
    .createQueryBuilder('user')
    .where('user.id IN (' + subQuery.getQuery() + ')')
    .setParameters(subQuery.getParameters()) // 继承子查询参数
    .getMany();
}

原生 SQL 片段(Raw)

复杂场景可嵌入原生 SQL(需谨慎,避免注入)

ts
async findUserWithRawSql() {
  return this.userRepository
    .createQueryBuilder('user')
    .select([
      'user.id',
      'user.name',
      // 原生 SQL:格式化时间
      this.dataSource.createQueryBuilder().raw('DATE_FORMAT(user.create_time, "%Y-%m-%d") as createDate'),
    ])
    .where(this.dataSource.createQueryBuilder().raw('user.age > 18')) // 原生条件
    .getRawMany();
}

事务

复杂业务需保证原子性时,结合 QueryRunner 实现事务

ts
async transactionDemo() {
  // 1. 创建 QueryRunner
  const queryRunner = this.dataSource.createQueryRunner();
  await queryRunner.connect();
  await queryRunner.startTransaction(); // 开启事务

  try {
    // 2. 事务内执行操作(需绑定 queryRunner)
    await queryRunner.manager
      .createQueryBuilder()
      .insert()
      .into(User)
      .values({ name: '王五', age: 25 })
      .execute();

    await queryRunner.manager
      .createQueryBuilder()
      .update(User)
      .set({ status: 0 })
      .where('id = :id', { id: 1 })
      .execute();

    // 3. 提交事务
    await queryRunner.commitTransaction();
  } catch (error) {
    // 4. 回滚事务
    await queryRunner.rollbackTransaction();
    throw error;
  } finally {
    // 5. 释放连接
    await queryRunner.release();
  }
}