如何使用 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 子句。这使得实现适当的接口来同时实现嵌套的 ANDOR 子句变得更加困难。为此(我假设)他们选择拆分 ANDOR 子句。这使得界面更具声明性,意味着您必须将 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
  }
  ....
]
})