来自结构化对象的 Typeorm 动态查询构建器
Posted
技术标签:
【中文标题】来自结构化对象的 Typeorm 动态查询构建器【英文标题】:Typeorm dynamic query builder from structured object 【发布时间】:2019-06-09 02:07:51 【问题描述】:为了在 graphql 服务器中使用,我定义了一个结构化输入类型,您可以在其中指定许多过滤条件,这些条件与 prisma 的工作方式非常相似:
这允许我在如下查询中提交结构化过滤器:
users(
where:
OR: [ email: starts_with: "ja" , email: ends_with: ".com" ],
AND: [ email: starts_with: "ja" , email: ends_with: ".com" ],
email: contains: "lowe"
)
id
email
在我的解析器中,我通过一个函数来提供 args.where 以解析结构并利用 TypeOrm 的查询构建器将其转换为正确的 sql。整个函数是:
import Brackets from "typeorm";
export const filterQuery = (query: any, where: any) =>
if (!where)
return query;
Object.keys(where).forEach(key =>
if (key === "OR")
where[key].map((queryArray: any) =>
query.orWhere(new Brackets(qb => filterQuery(qb, queryArray)));
);
else if (key === "AND")
where[key].map((queryArray: any) =>
query.andWhere(new Brackets(qb => filterQuery(qb, queryArray)));
);
else
const whereArgs = Object.entries(where);
whereArgs.map(whereArg =>
const [fieldName, filters] = whereArg;
const ops = Object.entries(filters);
ops.map(parameters =>
const [operation, value] = parameters;
switch (operation)
case "is":
query.andWhere(`$fieldName = :isvalue`, isvalue: value );
break;
case "not":
query.andWhere(`$fieldName != :notvalue`, notvalue: value );
break;
case "in":
query.andWhere(`$fieldName IN :invalue`, invalue: value );
break;
case "not_in":
query.andWhere(`$fieldName NOT IN :notinvalue`,
notinvalue: value
);
break;
case "lt":
query.andWhere(`$fieldName < :ltvalue`, ltvalue: value );
break;
case "lte":
query.andWhere(`$fieldName <= :ltevalue`, ltevalue: value );
break;
case "gt":
query.andWhere(`$fieldName > :gtvalue`, gtvalue: value );
break;
case "gte":
query.andWhere(`$fieldName >= :gtevalue`, gtevalue: value );
break;
case "contains":
query.andWhere(`$fieldName ILIKE :convalue`,
convalue: `%$value%`
);
break;
case "not_contains":
query.andWhere(`$fieldName NOT ILIKE :notconvalue`,
notconvalue: `%$value%`
);
break;
case "starts_with":
query
.andWhere(`$fieldName ILIKE :swvalue`)
.setParameter("swvalue", `$value%`);
break;
case "not_starts_with":
query
.andWhere(`$fieldName NOT ILIKE :nswvalue`)
.setParameter("nswvalue", `$value%`);
break;
case "ends_with":
query.andWhere(`$fieldName ILIKE :ewvalue`,
ewvalue: `%$value`
);
break;
case "not_ends_with":
query.andWhere(`$fieldName ILIKE :newvalue`,
newvalue: `%$value`
);
break;
default:
break;
);
);
);
return query;
;
哪个有效(有点)但没有像我期望的那样嵌套 AND/OR 查询(并且以前在 KNEX 中工作过)。以上函数生成SQL:
SELECT
"user"."id" AS "user_id",
"user"."name" AS "user_name",
"user"."email" AS "user_email",
"user"."loginToken" AS "user_loginToken",
"user"."loginTokenExpiry" AS "user_loginTokenExpiry",
"user"."active" AS "user_active",
"user"."visible" AS "user_visible",
"user"."isStaff" AS "user_isStaff",
"user"."isBilling" AS "user_isBilling",
"user"."createdAt" AS "user_createdAt",
"user"."updatedAt" AS "user_updatedAt",
"user"."version" AS "user_version"
FROM "user" "user"
WHERE (email ILIKE $1)
AND (email ILIKE $2)
OR (email ILIKE $3)
OR (email ILIKE $4)
AND email ILIKE $5
-- PARAMETERS: ["ja%","%.com","ja%","%.com","%lowe%"]
但我希望看到更多类似的东西:
.....
WHERE email ILIKE '%low%'
AND (
email ILIKE 'ja%' AND email ILIKE '%.com'
) AND (
email ILIKE 'ja%' OR email ILIKE '%.com'
)
请原谅无意义的重复查询。我只是想说明预期的 NESTED 语句。
如何强制查询构建器函数的 AND/OR 分支按预期正确嵌套?
** 如果有人可以帮助我找出此处的实际打字稿类型,则加分 **
【问题讨论】:
在您的示例查询中,有 id email,您确定这是您得到的,因为它似乎是一个 json,json 应该有-
将其拆分为 2 个函数,以便更轻松地添加类型
在您的案例陈述中,您需要做 orWhere 或 andWhere
不要在括号上映射,而是将其提升一层
import Brackets, WhereExpression, SelectQueryBuilder from "typeorm";
interface FieldOptions
starts_with?: string;
ends_with?: string;
contains?: string;
interface Fields
email?: FieldOptions;
interface Where extends Fields
OR?: Fields[];
AND?: Fields[];
const handleArgs = (
query: WhereExpression,
where: Where,
andOr: "andWhere" | "orWhere"
) =>
const whereArgs = Object.entries(where);
whereArgs.map(whereArg =>
const [fieldName, filters] = whereArg;
const ops = Object.entries(filters);
ops.map(parameters =>
const [operation, value] = parameters;
switch (operation)
case "is":
query[andOr](`$fieldName = :isvalue`, isvalue: value );
break;
case "not":
query[andOr](`$fieldName != :notvalue`, notvalue: value );
break;
case "in":
query[andOr](`$fieldName IN :invalue`, invalue: value );
break;
case "not_in":
query[andOr](`$fieldName NOT IN :notinvalue`,
notinvalue: value
);
break;
case "lt":
query[andOr](`$fieldName < :ltvalue`, ltvalue: value );
break;
case "lte":
query[andOr](`$fieldName <= :ltevalue`, ltevalue: value );
break;
case "gt":
query[andOr](`$fieldName > :gtvalue`, gtvalue: value );
break;
case "gte":
query[andOr](`$fieldName >= :gtevalue`, gtevalue: value );
break;
case "contains":
query[andOr](`$fieldName ILIKE :convalue`,
convalue: `%$value%`
);
break;
case "not_contains":
query[andOr](`$fieldName NOT ILIKE :notconvalue`,
notconvalue: `%$value%`
);
break;
case "starts_with":
query[andOr](`$fieldName ILIKE :swvalue`,
swvalue: `$value%`
);
break;
case "not_starts_with":
query[andOr](`$fieldName NOT ILIKE :nswvalue`,
nswvalue: `$value%`
);
break;
case "ends_with":
query[andOr](`$fieldName ILIKE :ewvalue`,
ewvalue: `%$value`
);
break;
case "not_ends_with":
query[andOr](`$fieldName ILIKE :newvalue`,
newvalue: `%$value`
);
break;
default:
break;
);
);
return query;
;
export const filterQuery = <T>(query: SelectQueryBuilder<T>, where: Where) =>
if (!where)
return query;
Object.keys(where).forEach(key =>
if (key === "OR")
query.andWhere(
new Brackets(qb =>
where[key]!.map(queryArray =>
handleArgs(qb, queryArray, "orWhere");
)
)
);
else if (key === "AND")
query.andWhere(
new Brackets(qb =>
where[key]!.map(queryArray =>
handleArgs(qb, queryArray, "andWhere");
)
)
);
);
return query;
;
【讨论】:
这太棒了。谢谢你本。唯一不包括的情况是根级别语句未嵌套在 AND 或 WHERE 下。在我上面的示例查询中,email: contains: "lowe"
被忽略。在您看来,我应该接受根级别的 where 语句还是我应该要求所有语句嵌套在 OR 或 AND 中? ```
现在,随着我不断改进,仅将 AND/OR 作为根级元素开始变得更有意义。这种方式更加明确,也没有太多不便。
如果您确实想在根级别添加它,您可以添加一个 else 来调用函数 else handleArgs(query, where, "andWhere")
这假设没有字段有重复的名称,但是,如何解决两个表连接并且函数必须区分第一个实体中的 id
和第二个实体中的 id
的情况没有出现错误的实体:Error: ER_NON_UNIQ_ERROR: Column 'id' in where clause is ambiguous
?
@benawad 对不起,你是the ben awad吗?【参考方案2】:
根据 Ben 的回答,我对函数进行了一些调整,以允许使用更通用的“filter”对象:
// enum
export enum Operator
AND = 'AND',
OR = 'OR',
// interfaces
interface FieldOptions
is?: string;
not?: string;
in?: string;
not_in?: string;
lt?: string;
lte?: string;
gt?: string;
gte?: string;
contains?: string;
not_contains?: string;
starts_with?: string;
not_starts_with?: string;
ends_with?: string;
not_ends_with?: string;
export interface Field
[key: string]: FieldOptions;
export type Where =
[K in Operator]?: (Where | Field)[];
;
// functions
export const filterQuery = <T>(query: SelectQueryBuilder<T>, where: Where) =>
if (!where)
return query;
else
return traverseTree(query, where) as SelectQueryBuilder<T>;
;
const traverseTree = (query: WhereExpression, where: Where, upperOperator = Operator.AND) =>
Object.keys(where).forEach((key) =>
if (key === Operator.OR)
query = query.orWhere(buildNewBrackets(where, Operator.OR));
else if (key === Operator.AND)
query = query.andWhere(buildNewBrackets(where, Operator.AND));
else
// Field
query = handleArgs(query, where as Field, upperOperator === Operator.AND ? 'andWhere' : 'orWhere');
);
return query;
;
const buildNewBrackets = (where: Where, operator: Operator) =>
return new Brackets((qb) =>
where[operator].map((queryArray) =>
traverseTree(qb, queryArray, operator);
),
);
;
const handleArgs = (query: WhereExpression, field: Field, andOr: 'andWhere' | 'orWhere') =>
...
;
这样我们现在可以将这种对象作为查询参数:
AND: [
OR: [
name:
is: 'John'
,
,
surname:
is: 'Doe'
,
]
,
AND: [
age:
gt: 30
,
,
type:
not: 'Employee'
]
,
registered_date:
gte: '2000-01-01'
]
生成的查询将是:
SELECT *
FROM users U
WHERE (U.name = 'John' OR U.surname = 'Doe') AND (U.age > 30 AND U.type != 'Employee') AND U.registered_date >= '2000-01-01';
【讨论】:
以上是关于来自结构化对象的 Typeorm 动态查询构建器的主要内容,如果未能解决你的问题,请参考以下文章
NestJS 中的 TypeORM - 如何在实体中获取 MySQL 嵌套对象并对子关系进行“位置”查询?
TypeORM select with case insensitive distinct