第七章 利用QueryBuilder 查询构造器 执行聚合查询
本章目标
- 知道什么时候
find选项不够用,需要请出 QueryBuilder - 熟练使用
createQueryBuilder("别名")拼装查询:select / where / orderBy / take / skip - 分清 getMany / getOne / getManyAndCount / getRawMany / getRawOne / getRawAndEntities 的使用场景
- 会用 QueryBuilder 做聚合统计、join 基础查询、子查询和 insert/update/delete
核心概念
repo.find({ where, order, take, skip }) 就像超市的标准套餐——常见需求都能满足,但菜单上没有的组合(聚合统计、多表 join、子查询)就点不了。
QueryBuilder 是"拼装 SQL 的积木":每个方法(.where()、.orderBy()、.groupBy()……)就是一块积木,链式拼下去,最后 .getMany() 一下执行。它的表达能力约等于 SQL 本身。
入口与别名规则
- 只能利用仓库的方式查询,不能直接利用实体类的方式查询
ts
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";
AppDataSource.initialize()
.then(async () => {
const userRepo = AppDataSource.getRepository(User);
const query = userRepo.createQueryBuilder("user"); // 这里query就是别名
const result = await query.getMany(); // 获取结果必须调用
console.log(JSON.stringify(result));
})
.catch((error) => console.log(error));where / andWhere / orWhere 与参数绑定
ts
const userRepo = AppDataSource.getRepository(User);
await userRepo
.createQueryBuilder("user")
.where("p.price >= :min", { min: 500 }) // :min 是参数占位符
.andWhere("p.stock > :stock", { stock: 10 })
.getMany();
await userRepo
.createQueryBuilder("user")
.where("p.category = :c1", { c1: "手机" })
.orWhere("p.price > :high", { high: 1000 })
.getMany();⚠️ 永远不要这样写:
.where("p.price > " + userInput)。字符串拼接 SQL 等于给 SQL 注入敞开大门。一律用:参数名占位 + 第二个参数传值。
注意:where 会覆盖之前的条件,andWhere/orWhere 是追加。
排序与分页
ts
const userRepo = AppDataSource.getRepository(User);
await userRepo
.createQueryBuilder("user")
.orderBy("user.firstName", "ASC") // 第一排序键
.addOrderBy("user.lastName", "DESC") // 第二排序键
.skip(5) // 跳过 5 条
.take(5) // 取 5 条 → 第 2 页
.getMany();六种"执行"方法怎么选
| 方法 | 返回值 | 使用场景 |
|---|---|---|
getMany() | Product[] | 查多条实体(最常用) |
getOne() | Product | null | 查一条,取不到返回 null,不报错 |
getManyAndCount() | [Product[], number] | 分页:count 不受 take/skip 影响 |
getRawMany() | any[] | 聚合/只选部分字段,只要原始行 |
getRawOne() | any | undefined | 只取一行原始数据(如总数统计) |
getRawAndEntities() | { entities, raw } | 既要实体、又要附加计算列时 |
ts
// getManyAndCount:分页标配
const [items, total] = await repo
.createQueryBuilder("p")
.where("p.category = :c", { c: "配件" })
.take(2)
.getManyAndCount(); // items 2 条,total 是该分类全部条数
// getRawAndEntities:实体 + 计算列一起拿
const { entities, raw } = await repo
.createQueryBuilder("p")
.select("p")
.addSelect("p.price * p.stock", "totalValue") // 计算列只出现在 raw 里
.where("p.id <= :id", { id: 3 })
.getRawAndEntities();5. 聚合统计:groupBy / having
聚合结果不是完整实体(实体里没有 avgPrice 这个字段),所以配 getRawMany():
ts
const stats = await repo
.createQueryBuilder("p")
.select("p.category", "category")
.addSelect("COUNT(p.id)", "cnt")
.addSelect("AVG(p.price)", "avgPrice")
.addSelect("SUM(p.stock)", "totalStock")
.groupBy("p.category")
.having("COUNT(p.id) >= :minCnt", { minCnt: 3 })
.orderBy("avgPrice", "DESC")
.getRawMany();
// stats = [{ category: "家电", cnt: "5", avgPrice: "650.00", ... }, ...]原生SQL
ts
import { MoreThan } from "typeorm";
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";
AppDataSource.initialize()
.then(async () => {
const result = await AppDataSource.manager.query(
"SELECT age from user group by age",
);
console.log(JSON.stringify(result));
})
.catch((error) => console.log(error));