使用查询构建器进行选择
什么是 QueryBuilder?
QueryBuilder 是 TypeORM 最强大的功能之一——
它允许你使用优雅且方便的语法构建 SQL 查询,
执行查询并自动转换为实体。
一个简单的 QueryBuilder 示例:
const firstUser = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne()
它生成了如下的 SQL 查询:
SELECT
user.id as userId,
user.firstName as userFirstName,
user.lastName as userLastName
FROM users user
WHERE user.id = 1
并返回一个 User 实例:
User {
id: 1,
firstName: "Timber",
lastName: "Saw"
}
使用 QueryBuilder 时的重要注意事项
使用 QueryBuilder 时,你需要为 WHERE 表达式中的参数指定唯一名称。以下写法不可行:
const result = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.linkedSheep", "linkedSheep")
.leftJoinAndSelect("user.linkedCow", "linkedCow")
.where("user.linkedSheep = :id", { id: sheepId })
.andWhere("user.linkedCow = :id", { id: cowId })
... 但这样写就可以:
const result = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.linkedSheep", "linkedSheep")
.leftJoinAndSelect("user.linkedCow", "linkedCow")
.where("user.linkedSheep = :sheepId", { sheepId })
.andWhere("user.linkedCow = :cowId", { cowId })
注意我们给 :sheepId 和 :cowId 指定了不同的名称,而不是两次使用 :id。
如何创建和使用 QueryBuilder?
可以通过多种方式创建 QueryBuilder:
-
使用 DataSource:
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.id = :id", { id: 1 })
.getOne() -
使用实体管理器:
const user = await dataSource.manager
.createQueryBuilder(User, "user")
.where("user.id = :id", { id: 1 })
.getOne() -
使用仓库:
const user = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne()
共有 5 种不同类型的 QueryBuilder:
-
SelectQueryBuilder—— 用于构建和执行SELECT查询。例如:const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.id = :id", { id: 1 })
.getOne() -
InsertQueryBuilder—— 用于构建和执行INSERT查询。例如:await dataSource
.createQueryBuilder()
.insert()
.into(User)
.values([
{ firstName: "Timber", lastName: "Saw" },
{ firstName: "Phantom", lastName: "Lancer" },
])
.execute() -
UpdateQueryBuilder—— 用于构建和执行UPDATE查询。例如:await dataSource
.createQueryBuilder()
.update(User)
.set({ firstName: "Timber", lastName: "Saw" })
.where("id = :id", { id: 1 })
.execute() -
DeleteQueryBuilder—— 用于构建和执行DELETE查询。例如:await dataSource
.createQueryBuilder()
.delete()
.from(User)
.where("id = :id", { id: 1 })
.execute() -
RelationQueryBuilder—— 用于构建和执行关系相关操作 [待定]。例如:await dataSource
.createQueryBuilder()
.relation(User, "photos")
.of(id)
.loadMany()
你可以在任何类型的 QueryBuilder 中切换到其它类型,切换后会得到一个新的 QueryBuilder 实例(与其它方法不同)。
使用 QueryBuilder 获取值
若只想从数据库获取单条结果,比如根据 id 或名称获取用户,需使用 getOne:
const timber = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id OR user.name = :name", { id: 1, name: "Timber" })
.getOne()
getOneOrFail 也获取单条结果,但无结果时会抛出 EntityNotFoundError:
const timber = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id OR user.name = :name", { id: 1, name: "Timber" })
.getOneOrFail()
若想获取多 条结果,比如获得所有用户,使用 getMany:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.getMany()
SelectQueryBuilder 返回两种类型的结果:实体 或 原始结果。
通常你会选择从数据库中选出真实的实体,如用户,使用 getOne 或 getMany。
但有时你只需选取特定数据,比如用户照片总数的 和。这类数据不是实体,称为原始数据,可用 getRawOne 和 getRawMany 获取。例如:
const { sum } = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("SUM(user.photosCount)", "sum")
.where("user.id = :id", { id: 1 })
.getRawOne()
const photosSums = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.id")
.addSelect("SUM(user.photosCount)", "sum")
.groupBy("user.id")
.getRawMany()
// 结果示例:[{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]
获取计数
可以使用 getCount() 获取查询返回的行数(数字形式),而非实体:
const count = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.name = :name", { name: "Timber" })
.getCount()
生成的 SQL 查询如下:
SELECT count(*) FROM users user WHERE user.name = 'Timber'
什么是别名?
我们写了 createQueryBuilder("user"),那 "user" 是什么?
它只是普通的 SQL 别名。除选中字段时,我们在各处都使用别名。
createQueryBuilder("user") 等价于:
createQueryBuilder().select("user").from(User, "user")
生成如下 SQL:
SELECT ... FROM users user
这里 users 是表名,user 是我们给表指定的别名,之后的查询中都会用它:
createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.name = :name", { name: "Timber" })
产生 SQL:
SELECT ... FROM users user WHERE user.name = 'Timber'
一个 QueryBuilder 不只限于一个别名,可以有多个。
每个 SELECT 可有自己的别名,
你可以从多张表查询,每张表各自带别名,
可以连接多张表,且都各有别名。
使用别名就是访问你选中的表或数据。
使用参数避免 SQL 注入
我们用了 where("user.name = :name", { name: "Timber" })。
这里 { name: "Timber" } 是参数,用于防止 SQL 注入。
我们本可以写成 where("user.name = '" + name + "')",
但这不安全,会导致 SQL 注入风险。
安全做法是:
where("user.name = :name", { name: "Timber" })
:name 是参数名,值在对象 里指定。
等同于:
where("user.name = :name")
.setParameter("name", "Timber")
注意:不要在同一查询中用同名参数赋予不同值,否则它们会被覆盖。
还可以传数组,变成 SQL 中的列表,使用特殊展开语法:
.where("user.name IN (:...names)", { names: [ "Timber", "Crystal", "Lina" ] })
转换为:
WHERE user.name IN ('Timber', 'Crystal', 'Lina')
添加 WHERE 表达式
添加 WHERE 很简单:
createQueryBuilder("user").where("user.name = :name", { name: "Timber" })
生成:
SELECT ... FROM users user WHERE user.name = 'Timber'
可以往已有的 WHERE 用 AND:
createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.andWhere("user.lastName = :lastName", { lastName: "Saw" })
生成:
SELECT ... FROM users user WHERE user.firstName = 'Timber' AND user.lastName = 'Saw'
也可以用 OR:
createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
生成:
SELECT ... FROM users user WHERE user.firstName = 'Timber' OR user.lastName = 'Saw'
可以用 IN 查询:
createQueryBuilder("user").where("user.id IN (:...ids)", { ids: [1, 2, 3, 4] })
生成:
SELECT ... FROM users user WHERE user.id IN (1, 2, 3, 4)
可以用 Brackets 组合复杂的 WHERE:
createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(
new Brackets((qb) => {
qb.where("user.firstName = :firstName", {
firstName: "Timber",
}).orWhere("user.lastName = :lastName", { lastName: "Saw" })
}),
)
生成:
SELECT ... FROM users user WHERE user.registered = true AND (user.firstName = 'Timber' OR user.lastName = 'Saw')
用 NotBrackets 加入带否定的复杂条件:
createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(
new NotBrackets((qb) => {
qb.where("user.firstName = :firstName", {
firstName: "Timber",
}).orWhere("user.lastName = :lastName", { lastName: "Saw" })
}),
)
生成:
SELECT ... FROM users user WHERE user.registered = true AND NOT((user.firstName = 'Timber' OR user.lastName = 'Saw'))
你能根据需要随意组合 AND、OR。
注意,调用多次 .where 会覆盖之前的条件。
提示:orWhere 与复杂的 AND、OR 表达式配 合时要小心,它们默认没有优先级,需要自行构造合适的字符串避免歧义。
添加 HAVING 表达式
添加 HAVING 同样简单:
createQueryBuilder("user").having("user.name = :name", { name: "Timber" })
生产:
SELECT ... FROM users user HAVING user.name = 'Timber'
可以添加 AND:
createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.andHaving("user.lastName = :lastName", { lastName: "Saw" })
生成:
SELECT ... FROM users user HAVING user.firstName = 'Timber' AND user.lastName = 'Saw'
添加 OR:
createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.orHaving("user.lastName = :lastName", { lastName: "Saw" })
生成:
SELECT ... FROM users user HAVING user.firstName = 'Timber' OR user.lastName = 'Saw'
同样地,多次 .having 会覆盖之前条件。
添加 ORDER BY 表达式
简单添加排序:
createQueryBuilder("user").orderBy("user.id")
生成:
SELECT ... FROM users user ORDER BY user.id
可以指定升降序:
createQueryBuilder("user").orderBy("user.id", "DESC")
createQueryBuilder("user").orderBy("user.id", "ASC")
添加多个排序条件:
createQueryBuilder("user").orderBy("user.name").addOrderBy("user.id")
或者用对象形式:
createQueryBuilder("user").orderBy({
"user.name": "ASC",
"user.id": "DESC",
})
再次调用 .orderBy 会覆盖之前的排序。
添加 DISTINCT ON 表达式(仅限 Postgres)
同时使用 distinct-on 和 order-by 时,distinct-on 中的字段必须匹配最左侧的 order-by 字段。
如果 distinct-on 没有 order-by,结果的第一行是不可预测的。
语法示例如:
createQueryBuilder("user").distinctOn(["user.id"]).orderBy("user.id")
生成:
SELECT DISTINCT ON (user.id) ... FROM users user ORDER BY user.id
添加 GROUP BY 表达式
简单添加分组:
createQueryBuilder("user").groupBy("user.id")
生成:
SELECT ... FROM users user GROUP BY user.id
添加更多分组条件用 addGroupBy:
createQueryBuilder("user").groupBy("user.name").addGroupBy("user.id")
再次调用 .groupBy 会覆盖之前所有分组。
添加 LIMIT 表达式
简单限制数量:
createQueryBuilder("user").limit(10)
生成:
SELECT ... FROM users user LIMIT 10
不同数据库的查询会不同。
注意:对于复杂查询(有连接或子查询),LIMIT 可能表现不如预期。
分页推荐使用 take。
添加 OFFSET 表达式
跳过多少行:
createQueryBuilder("user").offset(10)
生成:
SELECT ... FROM users user OFFSET 10
注意同 LIMIT,复杂查询时 OFFSET 可能不准,分页建议使用 skip。
连接关系
假设如下实体:
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany((type) => Photo, (photo) => photo.user)
photos: Photo[]
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"
import { User } from "./User"
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
url: string
@ManyToOne((type) => User, (user) => user.photos)
user: User
}
现在你想加载用户 "Timber"及其所有照片:
const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne()
结果为:
{
id: 1,
name: "Timber",
photos: [{
id: 1,
url: "me-with-chakram.jpg"
}, {
id: 2,
url: "me-with-trees.jpg"
}]
}
leftJoinAndSelect 会自动加载 Timber 的所有照片。
第一个参数是关联名称,第二个是关联表的别名,可在查询中使用。
比如查询未被移除的照片:
const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.andWhere("photo.isRemoved = :isRemoved", { isRemoved: false })
.getOne()
生成:
SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber' AND photo.isRemoved = FALSE
你也可以把条件写到连接语句:
const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", {
isRemoved: false,
})
.where("user.name = :name", { name: "Timber" })
.getOne()
生成:
SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'
内连接和左连接
如果想用 INNER JOIN 而非 LEFT JOIN,用 innerJoinAndSelect:
const user = await createQueryBuilder("user")
.innerJoinAndSelect(
"user.photos",
"photo",
"photo.isRemoved = :isRemoved",
{ isRemoved: false },
)
.where("user.name = :name", { name: "Timber" })
.getOne()
生成:
SELECT user.*, photo.* FROM users user
INNER JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'
INNER JOIN 不返回没有照片的用户,LEFT JOIN 会返回。
更详尽的连接介绍可参考 SQL 文档。
无选择的连接
你也可以连接数据但不选取:
const user = await createQueryBuilder("user")
.innerJoin("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne()
结果是:
SELECT user.* FROM users user
INNER JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber'
这样只会选中用户,不返回照片。
连接任意实体或表
不仅能连接关联实体,也能连接无关联的实体或表,例如:
const user = await createQueryBuilder("user")
.leftJoinAndSelect(Photo, "photo", "photo.userId = user.id")
.getMany()
或者:
const user = await createQueryBuilder("user")
.leftJoinAndSelect("photos", "photo", "photo.userId = user.id")
.getMany()
连接并映射功能
给 User 实体加个 profilePhoto 属性,并用 QueryBuilder 把数据映射进来:
export class User {
/// ...
profilePhoto: Photo
}
const user = await createQueryBuilder("user")
.leftJoinAndMapOne(
"user.profilePhoto",
"user.photos",
"photo",
"photo.isForProfile = TRUE",
)
.where("user.name = :name", { name: "Timber" })
.getOne()
它会加载 Timber 的头像照片并赋值到 user.profilePhoto。
若要加载并映射单个实体,使用 leftJoinAndMapOne;
加载多个实体,使用 leftJoinAndMapMany。
获取生成的查询
有时想看 QueryBuilder 生成的 SQL 查询,可用 getSql:
const sql = createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
.getSql()
调试时可用 printSql:
const users = await createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
.printSql()
.getMany()
查询结果会返回用户,同时打印 SQL 到控制台。