跳至主内容区

使用 Query Builder 进行查询

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

什么是 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?

有几种方式可以创建 Query Builder

  • 使用 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 获取数据

要从数据库中获取单条结果, 例如通过 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()

使用查询构建器可以获取两种类型的结果:实体原始数据。 大多数情况下,您需要从数据库中选择真实实体,例如用户。 为此,您可以使用 getOnegetMany。 但有时您需要选择特定数据,比如_所有用户照片的总数_。 这类数据不是实体,称为原始数据。 要获取原始数据,请使用 getRawOnegetRawMany。 示例如下:

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()

// result will be like this: [{ 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

在此 SQL 查询中,users 是表名,user 是我们为该表分配的别名。 稍后我们使用该别名访问表:

createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.name = :name", { name: "Timber" })

这将生成以下 SQL 查询:

SELECT ... FROM users user WHERE user.name = 'Timber'

注意,我们通过创建查询构建器时分配的 user 别名来使用 users 表。

一个查询构建器不限于单个别名,它们可以有多个别名。 每个 SELECT 语句可以有独立别名, 您可以从多个表中选择,每个表都有独立别名, 可以连接多个表,每个表都有独立别名。 您可以使用这些别名访问正在选择的表(或正在选择的数据)。

使用参数转义数据

我们使用了 where("user.name = :name", { name: "Timber" }){ name: "Timber" } 代表什么?这是用于防止 SQL 注入的参数。 我们本可以写成:where("user.name = '" + name + "'), 但这不安全,会导致代码面临 SQL 注入风险。 安全的方式是使用特殊语法:where("user.name = :name", { name: "Timber" }), 其中 :name 是参数名称,值在对象中指定:{ name: "Timber" }

.where("user.name = :name", { name: "Timber" })

是以下写法的简写形式:

.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" })

这将生成以下 SQL 查询:

SELECT ... FROM users user WHERE user.firstName = 'Timber' AND user.lastName = 'Saw'

可以在现有 WHERE 表达式中添加 OR 条件:

createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })

这将生成以下 SQL 查询:

SELECT ... FROM users user WHERE user.firstName = 'Timber' OR user.lastName = 'Saw'

可以在 WHERE 表达式中使用 IN 查询:

createQueryBuilder("user").where("user.id IN (:...ids)", { ids: [1, 2, 3, 4] })

这将生成以下 SQL 查询:

SELECT ... FROM users user WHERE user.id IN (1, 2, 3, 4)

可以使用 Brackets 向现有 WHERE 添加复杂的 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" })
}),
)

这将生成以下 SQL 查询:

SELECT ... FROM users user WHERE user.registered = true AND (user.firstName = 'Timber' OR user.lastName = 'Saw')

可以使用 NotBrackets 向现有 WHERE 添加取反的复杂 WHERE 表达式

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" })
}),
)

这将生成以下 SQL 查询:

SELECT ... FROM users user WHERE user.registered = true AND NOT((user.firstName = 'Timber' OR user.lastName = 'Saw'))

你可以根据需要组合任意多个 ANDOR 表达式。如果多次使用 .where 方法,将会覆盖之前的所有 WHERE 表达式。

注意:使用 orWhere 时要特别小心——当处理同时包含 ANDOR 的复杂表达式时,请记住这些表达式会直接堆叠而没有优先级处理。有时你可能需要创建完整的 where 字符串来替代,避免使用 orWhere

添加 HAVING 表达式

添加 HAVING 表达式非常简单:

createQueryBuilder("user").having("user.name = :name", { name: "Timber" })

这将生成以下 SQL 查询:

SELECT ... FROM users user HAVING user.name = 'Timber'

可以在现有 HAVING 表达式中添加 AND 条件:

createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.andHaving("user.lastName = :lastName", { lastName: "Saw" })

这将生成以下 SQL 查询:

SELECT ... FROM users user HAVING user.firstName = 'Timber' AND user.lastName = 'Saw'

可以在现有 HAVING 表达式中添加 OR 条件:

createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.orHaving("user.lastName = :lastName", { lastName: "Saw" })

这将生成以下 SQL 查询:

SELECT ... FROM users user HAVING user.firstName = 'Timber' OR user.lastName = 'Saw'

你可以根据需要组合任意多个 ANDOR 表达式。如果多次使用 .having 方法,将会覆盖之前的所有 HAVING 表达式。

添加 ORDER BY 表达式

添加 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 方法,将会覆盖之前的所有 ORDER BY 表达式。

添加 DISTINCT ON 表达式(仅限 Postgres)

当同时使用 distinct-on 和 order-by 表达式时,distinct-on 表达式必须与最左侧的 order-by 字段匹配。distinct-on 表达式的解释规则与 order-by 相同。请注意:在没有 order-by 表达式的情况下使用 distinct-on 意味着每个集合的第一行结果是不可预测的。

添加 DISTINCT ON 表达式非常简单:

createQueryBuilder("user").distinctOn(["user.id"]).orderBy("user.id")

这将生成:

SELECT DISTINCT ON (user.id) ... FROM users user ORDER BY user.id

添加 GROUP BY 表达式

添加 GROUP BY 表达式非常简单:

createQueryBuilder("user").groupBy("user.id")

这将生成以下 SQL 查询:

SELECT ... FROM users user GROUP BY user.id

使用 addGroupBy 添加更多分组条件:

createQueryBuilder("user").groupBy("user.name").addGroupBy("user.id")

如果多次使用 .groupBy 方法,将会覆盖之前的所有 GROUP BY 表达式。

添加 LIMIT 表达式

添加 LIMIT 表达式非常简单:

createQueryBuilder("user").limit(10)

这将生成以下 SQL 查询:

SELECT ... FROM users user LIMIT 10

最终生成的 SQL 查询取决于数据库类型(SQL、MySQL、Postgres 等)。注意:在包含连接或子查询的复杂查询中,LIMIT 可能不会按预期工作。如果实现分页功能,建议使用 take 方法替代。

添加 OFFSET 表达式

添加 SQL OFFSET 表达式非常简单:

createQueryBuilder("user").offset(10)

这将生成以下 SQL 查询:

SELECT ... FROM users user OFFSET 10

最终生成的 SQL 查询取决于数据库类型(SQL、MySQL、Postgres 等)。注意:在包含连接或子查询的复杂查询中,OFFSET 可能不会按预期工作。如果实现分页功能,建议使用 skip 方法替代。

关联查询(Joining relations)

假设你有以下实体:

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 的所有照片。第一个参数是要加载的关系,第二个参数是为该关系表分配的别名。你可以在查询构建器的任何位置使用这个别名。例如,让我们获取 Timber 所有未被删除的照片:

const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.andWhere("photo.isRemoved = :isRemoved", { isRemoved: false })
.getOne()

这将生成以下 SQL 查询:

SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber' AND photo.isRemoved = FALSE

你也可以在连接表达式中直接添加条件,而不是使用 "where":

const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", {
isRemoved: false,
})
.where("user.name = :name", { name: "Timber" })
.getOne()

这将生成如下 SQL 查询:

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'

LEFT JOININNER JOIN 的区别在于:当用户没有照片时,INNER JOIN 不会返回该用户,而 LEFT JOIN 仍会返回用户数据。要了解更多连接类型,请参阅 SQL 文档

不选择数据的连接

你可以连接数据但不选择它们,使用 leftJoininnerJoin

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'

如果 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 语句。

获取原始结果

使用查询构建器可获取两类结果:实体原始数据。多数情况下你需要查询数据库中的实际实体(如用户),此时应使用 getOnegetMany。但有时需查询特定数据(如用户照片总数),这类非实体数据称为原始数据,使用 getRawOnegetRawMany 获取:

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()

// result will be like this: [{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]

流式处理结果数据

可通过 stream 获取数据流。流式处理返回原始数据,需手动处理实体转换:

const stream = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.stream()

使用分页

开发应用时通常需要分页功能,适用于分页导航、页面滑块或无限滚动组件:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.take(10)
.getMany()

这将返回前 10 位用户及其照片。

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.skip(10)
.getMany()

这将返回除前 10 位外的所有用户及其照片。可组合使用这些方法:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.skip(5)
.take(10)
.getMany()

这将跳过前 5 位用户并获取随后的 10 位用户。

虽然 takeskip 看起来像是我们在使用 limitoffset,但它们并不是。limitoffset 在涉及连接或子查询的复杂查询中可能不会如你所愿地工作。使用 takeskip 将会避免这些问题。

设置锁机制

查询构建器支持乐观锁和悲观锁。

锁模式

支持的锁模式及其对应的 SQL 语句如下表所示(空白单元格表示不支持)。当指定不支持的锁模式时,将抛出 LockNotSupportedOnGivenDriverError 错误。

|                 | pessimistic_read                  | pessimistic_write       | dirty_read    | pessimistic_partial_write (Deprecated, use onLocked instead)   | pessimistic_write_or_fail (Deprecated, use onLocked instead)   | for_no_key_update   | for_key_share |
| --------------- | --------------------------------- | ----------------------- | ------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | ------------------- | ------------- |
| MySQL | FOR SHARE (8+)/LOCK IN SHARE MODE | FOR UPDATE | (nothing) | FOR UPDATE SKIP LOCKED | FOR UPDATE NOWAIT | | |
| Postgres | FOR SHARE | FOR UPDATE | (nothing) | FOR UPDATE SKIP LOCKED | FOR UPDATE NOWAIT | FOR NO KEY UPDATE | FOR KEY SHARE |
| Oracle | FOR UPDATE | FOR UPDATE | (nothing) | | | | |
| SQL Server | WITH (HOLDLOCK, ROWLOCK) | WITH (UPDLOCK, ROWLOCK) | WITH (NOLOCK) | | | | |
| AuroraDataApi | LOCK IN SHARE MODE | FOR UPDATE | (nothing) | | | | |
| CockroachDB | | FOR UPDATE | (nothing) | | FOR UPDATE NOWAIT | FOR NO KEY UPDATE | |

使用悲观读锁的方法:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_read")
.getMany()

使用悲观写锁的方法:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.getMany()

使用脏读锁的方法:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("dirty_read")
.getMany()

使用乐观锁的方法:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("optimistic", existUser.version)
.getMany()

乐观锁需与 @Version@UpdatedDate 装饰器配合使用。

表锁定

可通过以下方法锁定表:

const users = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.leftJoin("post.author", "user")
.setLock("pessimistic_write", undefined, ["post"])
.getMany()

若提供表锁定参数,则仅对 FOR UPDATE OF 子句中指定的表进行锁定。

setOnLocked

用于控制行被锁定时的处理行为。默认情况下数据库会等待锁释放,可通过 setOnLocked 调整该行为:

不等待:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.setOnLocked("nowait")
.getMany()

跳过锁定行:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.setOnLocked("skip_locked")
.getMany()

各数据库对 setOnLocked 的支持情况(基于锁模式):

  • Postgres:pessimistic_read, pessimistic_write, for_no_key_update, for_key_share

  • MySQL 8+:pessimistic_read, pessimistic_write

  • MySQL < 8 和 Maria DB:pessimistic_write

  • Cockroach:pessimistic_write(仅支持 nowait

使用自定义索引

某些情况下可为数据库服务器指定特定索引,此功能目前仅 MySQL 支持。

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.useIndex("my_index") // name of index
.getMany()

最大执行时间

可通过设置超时终止慢查询,避免服务器崩溃:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.maxExecutionTime(1000) // milliseconds.
.getMany()

部分字段选择

若仅需选择实体的部分属性,可使用以下语法:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select(["user.id", "user.name"])
.getMany()

这将仅查询 Useridname 字段。

使用子查询

可便捷地在 FROMWHEREJOIN 表达式中创建子查询。例如:

const qb = await dataSource.getRepository(Post).createQueryBuilder("post")

const posts = qb
.where(
"post.title IN " +
qb
.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery(),
)
.setParameter("registered", true)
.getMany()

更优雅的实现方式:

const posts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.where((qb) => {
const subQuery = qb
.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery()
return "post.title IN " + subQuery
})
.setParameter("registered", true)
.getMany()

也可单独创建查询构建器并使用其生成的 SQL:

const userQb = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.name")
.where("user.registered = :registered", { registered: true })

const posts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.where("post.title IN (" + userQb.getQuery() + ")")
.setParameters(userQb.getParameters())
.getMany()

FROM 中创建子查询:

const userQb = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.name", "name")
.where("user.registered = :registered", { registered: true })

const posts = await dataSource
.createQueryBuilder()
.select("user.name", "name")
.from("(" + userQb.getQuery() + ")", "user")
.setParameters(userQb.getParameters())
.getRawMany()

或使用更简洁的语法:

const posts = await dataSource
.createQueryBuilder()
.select("user.name", "name")
.from((subQuery) => {
return subQuery
.select("user.name", "name")
.from(User, "user")
.where("user.registered = :registered", { registered: true })
}, "user")
.getRawMany()

若需添加"第二来源"的子查询,请使用 addFrom

子查询同样适用于 SELECT 语句:

const posts = await dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect((subQuery) => {
return subQuery.select("user.name", "name").from(User, "user").limit(1)
}, "name")
.from(Post, "post")
.getRawMany()

隐藏字段查询

若查询的模型包含 select: false 的字段,必须使用 addSelect 方法才能获取该字段值。

假设存在以下实体:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@Column({ select: false })
password: string
}

使用标准 find 或查询时不会返回 password 属性。但通过以下方式:

const users = await dataSource
.getRepository(User)
.createQueryBuilder()
.select("user.id", "id")
.addSelect("user.password")
.getMany()

即可在查询结果中获取 password 属性。

查询软删除记录

若查询的模型包含 @DeleteDateColumn 属性,查询构建器将自动过滤已被"软删除"的行。

假设存在以下实体:

import {
Entity,
PrimaryGeneratedColumn,
Column,
DeleteDateColumn,
} from "typeorm"

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@DeleteDateColumn()
deletedAt?: Date
}

使用标准的 find 方法或普通查询时,您将无法获取该列中有值的行。但如果您执行以下操作:

const users = await dataSource
.getRepository(User)
.createQueryBuilder()
.select("user.id", "id")
.withDeleted()
.getMany()

您将获得所有行,包括已被删除的行。

通用表表达式

QueryBuilder 实例 支持通用表表达式, 前提是您的数据库最低支持版本包含此功能。Oracle 数据库暂不支持通用表表达式。

const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.select("user.id", "id")
.addCommonTableExpression(
`
SELECT "userId" FROM "post"
`,
"post_users_ids",
)
.where(`user.id IN (SELECT "userId" FROM 'post_users_ids')`)
.getMany()

在 Postgres 中可使用 InsertQueryBuilderUpdateQueryBuilder 的返回值:

const insertQueryBuilder = connection
.getRepository(User)
.createQueryBuilder()
.insert({
name: "John Smith",
})
.returning(["id"])

const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.addCommonTableExpression(insertQueryBuilder, "insert_results")
.where(`user.id IN (SELECT "id" FROM 'insert_results')`)
.getMany()

时间旅行查询

时间旅行查询 目前仅支持 CockroachDB 数据库。

const repository = connection.getRepository(Account)

// create a new account
const account = new Account()
account.name = "John Smith"
account.balance = 100
await repository.save(account)

// imagine we update the account balance 1 hour after creation
account.balance = 200
await repository.save(account)

// outputs { name: "John Smith", balance: "200" }
console.log(account)

// load account state on 1 hour back
account = await repository
.createQueryBuilder("account")
.timeTravelQuery(`'-1h'`)
.getOneOrFail()

// outputs { name: "John Smith", balance: "100" }
console.log(account)

默认情况下,若未传递参数,timeTravelQuery() 会使用 follower_read_timestamp() 函数。 有关其他支持的时间戳参数及更多信息,请参阅 CockroachDB 文档

调试

您可以通过调用 getQuery()getQueryAndParameters() 获取查询构建器生成的 SQL。

若只需查询语句,可使用 getQuery()

const sql = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getQuery()

这将返回:

SELECT `user`.`id` as `userId`, `user`.`firstName` as `userFirstName`, `user`.`lastName` as `userLastName` FROM `users` `user` WHERE `user`.`id` = ?

若需同时获取查询语句和参数,可通过 getQueryAndParameters() 返回数组

const queryAndParams = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getQueryAndParameters()

这将返回:

;[
"SELECT `user`.`id` as `userId`, `user`.`firstName` as `userFirstName`, `user`.`lastName` as `userLastName` FROM `users` `user` WHERE `user`.`id` = ?",
[1],
]