如何使用 typeORM 制作复杂的嵌套 where 条件?
How to make complex nested where conditions with typeORM?
我有多个嵌套的 where 条件,并希望在不使用 typeORM 进行太多代码重复的情况下生成它们。
SQL where 条件应该是这样的:
WHERE "Table"."id" =
AND
"Table"."notAvailable" IS NULL
AND
(
"Table"."date" >
OR
(
"Table"."date" =
AND
"Table"."myId" >
)
)
AND
(
"Table"."created" =
OR
"Table"."updated" =
)
AND
(
"Table"."text" ilike '%search%'
OR
"Table"."name" ilike '%search%'
)
但是使用 FindConditions
似乎不可能使它们嵌套,因此我必须在 FindConditions 数组中使用 AND
的所有可能组合。而且不可能将它拆分为 .where()
和 .andWhere()
,因为 andWhere
不能使用对象文字。
是否有另一种可能在不使用 Raw SQL 的情况下使用 typeORM 实现此查询?
我认为您正在混合使用 2 种从 TypeORM 检索实体的方法,find 来自存储库和查询构建器。 FindConditions
用于 find 函数。 andWhere
函数由查询构建器使用。在构建更复杂的查询时,通常 better/easier 使用查询构建器。
Query builder
使用查询构建时,您可以更加自由地确保查询符合您的需要。您可以随意添加任何 SQL:
const desiredEntity = await connection
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.andWhere("user.date > :date OR (user.date = :date AND user.myId = :myId)",
{
date: specificCreatedAtDate,
myId: mysteryId,
})
.getOne();
请注意,根据您使用的数据库,您在此处使用的实际 SQL 需要兼容。随之而来的还有使用这种方法的一个可能的缺点。您会将您的项目绑定到特定的数据库。确保阅读有关 aliases 的表格的信息,如果您使用的是关系,那么您可以设置这些表格,这会很方便。
Repository
您已经看到这不太舒服。这是因为 find
函数或更具体的 findOptions
正在使用对象来构建 where 子句。这使得实现适当的接口来同时实现嵌套的 AND
和 OR
子句变得更加困难。为此(我假设)他们选择拆分 AND
和 OR
子句。这使得界面更具声明性,意味着您必须将 OR
子句拉到顶部:
const desiredEntity = await repository.find({
where: [{
id: id,
notAvailable: Not(IsNull()),
date: MoreThan(date)
},{
id: id,
notAvailable: Not(IsNull()),
date: date
myId: myId
}]
})
我无法想象查看所需查询的大小时此代码会非常高效。
或者您可以使用 Raw
查找助手。这将需要您为每个字段重写您的子句,因为您一次只能访问一个别名。您可以猜测列名或别名,但这是非常糟糕的做法并且非常不稳定,因为您无法直接轻松地控制它。
在使用 queryBuilder 时,我建议使用 Brackets
如 Typeorm 文档中所述:https://typeorm.io/#/select-query-builder/adding-where-expression
你可以这样做:
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')
如果你想在满足条件的情况下嵌套 andWhere 语句,这里是一个例子:
async getTasks(filterDto: GetTasksFilterDto, user: User): Promise<Task[]> {
const { status, search } = filterDto;
/* create a query using the query builder */
// task is what refer to the Task entity
const query = this.createQueryBuilder('task');
// only get the tasks that belong to the user
query.where('task.userId = :userId', { userId: user.id });
/* if status is defined then add a where clause to the query */
if (status) {
// :<variable-name> is a placeholder for the second object key value pair
query.andWhere('task.status = :status', { status });
}
/* if search is defined then add a where clause to the query */
if (search) {
query.andWhere(
/*
LIKE: find a similar match (doesn't have to be exact)
- https://www.w3schools.com/sql/sql_like.asp
Lower is a sql method
- https://www.w3schools.com/sql/func_sqlserver_lower.asp
* bug: search by pass where userId; fix: () whole addWhere statement
because andWhere stiches the where class together, add () to make andWhere with or and like into a single where statement
*/
'(LOWER(task.title) LIKE LOWER(:search) OR LOWER(task.description) LIKE LOWER(:search))',
// :search is like a param variable, and the search object is the key value pair. Both have to match
{ search: `%${search}%` },
);
}
/* execute the query
- getMany means that you are expecting an array of results
*/
let tasks;
try {
tasks = await query.getMany();
} catch (error) {
this.logger.error(
`Failed to get tasks for user "${
user.username
}", Filters: ${JSON.stringify(filterDto)}`,
error.stack,
);
throw new InternalServerErrorException();
}
return tasks;
}
我有一份清单
{
date: specificCreatedAtDate,
userId: mysteryId
}
我的解决方案是
.andWhere(
new Brackets((qb) => {
qb.where(
'userTable.date = :date0 AND userTable.type = :userId0',
{
date0: dates[0].date,
userId0: dates[0].type,
}
);
for (let i = 1; i < dates.length; i++) {
qb.orWhere(
`userTable.date = :date${i} AND userTable.userId = :userId${i}`,
{
[`date${i}`]: dates[i].date,
[`userId${i}`]: dates[i].userId,
}
);
}
})
)
那会产生类似的东西
const userEntity = await repository.find({
where: [{
userId: id0,
date: date0
},{
id: id1,
userId: date1
}
....
]
})
我有多个嵌套的 where 条件,并希望在不使用 typeORM 进行太多代码重复的情况下生成它们。
SQL where 条件应该是这样的:
WHERE "Table"."id" =
AND
"Table"."notAvailable" IS NULL
AND
(
"Table"."date" >
OR
(
"Table"."date" =
AND
"Table"."myId" >
)
)
AND
(
"Table"."created" =
OR
"Table"."updated" =
)
AND
(
"Table"."text" ilike '%search%'
OR
"Table"."name" ilike '%search%'
)
但是使用 FindConditions
似乎不可能使它们嵌套,因此我必须在 FindConditions 数组中使用 AND
的所有可能组合。而且不可能将它拆分为 .where()
和 .andWhere()
,因为 andWhere
不能使用对象文字。
是否有另一种可能在不使用 Raw SQL 的情况下使用 typeORM 实现此查询?
我认为您正在混合使用 2 种从 TypeORM 检索实体的方法,find 来自存储库和查询构建器。 FindConditions
用于 find 函数。 andWhere
函数由查询构建器使用。在构建更复杂的查询时,通常 better/easier 使用查询构建器。
Query builder
使用查询构建时,您可以更加自由地确保查询符合您的需要。您可以随意添加任何 SQL:
const desiredEntity = await connection
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.andWhere("user.date > :date OR (user.date = :date AND user.myId = :myId)",
{
date: specificCreatedAtDate,
myId: mysteryId,
})
.getOne();
请注意,根据您使用的数据库,您在此处使用的实际 SQL 需要兼容。随之而来的还有使用这种方法的一个可能的缺点。您会将您的项目绑定到特定的数据库。确保阅读有关 aliases 的表格的信息,如果您使用的是关系,那么您可以设置这些表格,这会很方便。
Repository
您已经看到这不太舒服。这是因为 find
函数或更具体的 findOptions
正在使用对象来构建 where 子句。这使得实现适当的接口来同时实现嵌套的 AND
和 OR
子句变得更加困难。为此(我假设)他们选择拆分 AND
和 OR
子句。这使得界面更具声明性,意味着您必须将 OR
子句拉到顶部:
const desiredEntity = await repository.find({
where: [{
id: id,
notAvailable: Not(IsNull()),
date: MoreThan(date)
},{
id: id,
notAvailable: Not(IsNull()),
date: date
myId: myId
}]
})
我无法想象查看所需查询的大小时此代码会非常高效。
或者您可以使用 Raw
查找助手。这将需要您为每个字段重写您的子句,因为您一次只能访问一个别名。您可以猜测列名或别名,但这是非常糟糕的做法并且非常不稳定,因为您无法直接轻松地控制它。
在使用 queryBuilder 时,我建议使用 Brackets
如 Typeorm 文档中所述:https://typeorm.io/#/select-query-builder/adding-where-expression
你可以这样做:
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')
如果你想在满足条件的情况下嵌套 andWhere 语句,这里是一个例子:
async getTasks(filterDto: GetTasksFilterDto, user: User): Promise<Task[]> {
const { status, search } = filterDto;
/* create a query using the query builder */
// task is what refer to the Task entity
const query = this.createQueryBuilder('task');
// only get the tasks that belong to the user
query.where('task.userId = :userId', { userId: user.id });
/* if status is defined then add a where clause to the query */
if (status) {
// :<variable-name> is a placeholder for the second object key value pair
query.andWhere('task.status = :status', { status });
}
/* if search is defined then add a where clause to the query */
if (search) {
query.andWhere(
/*
LIKE: find a similar match (doesn't have to be exact)
- https://www.w3schools.com/sql/sql_like.asp
Lower is a sql method
- https://www.w3schools.com/sql/func_sqlserver_lower.asp
* bug: search by pass where userId; fix: () whole addWhere statement
because andWhere stiches the where class together, add () to make andWhere with or and like into a single where statement
*/
'(LOWER(task.title) LIKE LOWER(:search) OR LOWER(task.description) LIKE LOWER(:search))',
// :search is like a param variable, and the search object is the key value pair. Both have to match
{ search: `%${search}%` },
);
}
/* execute the query
- getMany means that you are expecting an array of results
*/
let tasks;
try {
tasks = await query.getMany();
} catch (error) {
this.logger.error(
`Failed to get tasks for user "${
user.username
}", Filters: ${JSON.stringify(filterDto)}`,
error.stack,
);
throw new InternalServerErrorException();
}
return tasks;
}
我有一份清单
{
date: specificCreatedAtDate,
userId: mysteryId
}
我的解决方案是
.andWhere(
new Brackets((qb) => {
qb.where(
'userTable.date = :date0 AND userTable.type = :userId0',
{
date0: dates[0].date,
userId0: dates[0].type,
}
);
for (let i = 1; i < dates.length; i++) {
qb.orWhere(
`userTable.date = :date${i} AND userTable.userId = :userId${i}`,
{
[`date${i}`]: dates[i].date,
[`userId${i}`]: dates[i].userId,
}
);
}
})
)
那会产生类似的东西
const userEntity = await repository.find({
where: [{
userId: id0,
date: date0
},{
id: id1,
userId: date1
}
....
]
})