ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

NocoBase 数据库 Filter 操作符完全指南:从 $eq 到 $dateOn 的 40+ 查询运算符详解

NocoBase 数据库 Filter 操作符完全指南:从 $eq 到 $dateOn 的 40+ 查询运算符详解 NocoBase 数据库 Filter 操作符完全指南从 $eq 到 $dateOn 的 40 查询运算符详解【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase导读本文以 NocoBase 官方数据库 API 文档 operators.md 为骨架系统讲解 Repository 的find/findOne/findAndCount/count等接口filter参数中全部内置运算符的语义、SQL 对应关系、适用字段类型与实战示例并结合 packages/core/database/src/operators 目录下的源码实现与测试用例深入揭示每个运算符在 PostgreSQL / MySQL / SQLite 等不同数据库下的底层行为。读完本文你将能熟练编写任意复杂度的 NocoBase 数据过滤条件并掌握通过db.registerOperators()扩展自定义运算符的方法。一、Filter 参数与运算符的 JSON 化设计在 NocoBase 中Repository是数据访问的核心入口其查询方法都接受一个filter参数用于描述过滤条件const repository db.getRepository(books); repository.find({ filter: { title: { $eq: 春秋, }, }, });为了让过滤条件能够被 JSON 序列化、跨 HTTP API 传输NocoBase 将查询运算符统一表示为以$为前缀的字符串标识例如$eq、$in、$and。这样filter就是一个纯 JSON 结构既可以在服务端代码中直接书写也可以由前端通过 API 请求参数动态传入。运算符注册的底层机制从源码看运算符的注册发生在Database类的initOperators()方法中database.tsinitOperators() { const operators new Map(); // Sequelize 内置 for (const key in Op) { operators.set($ key, Op[key]); const val Utils.underscoredIf(key, true); operators.set($ val, Op[key]); operators.set($ val.replace(/_/g, ), Op[key]); } this.operators operators; this.registerOperators({ ...(extendOperators as unknown as MapOfOperatorFunc), }); }这里有两个层次Sequelize 内置运算符兜底NocoBase 底层基于 Sequelize ORM启动时会把 SequelizeOp中的全部符号如Op.like、Op.between、Op.regexp、Op.col、Op.is、Op.not等批量注册为对应的$前缀字符串。这就是$like、$notLike、$iLike、$regexp、$col、$is、$not、$gt、$between等运算符的来源。NocoBase 自定义实现覆盖随后通过registerOperators()注册 NocoBase 自研的运算符实现见 operators/index.ts这些实现会覆盖同名的 Sequelize 默认行为并新增$dateOn、$isFalsy、$match、$exists等 NocoBase 专属运算符// packages/core/database/src/operators/index.ts export default { ...association, // $exists / $notExists ...date, // $dateOn / $dateBefore / $dateAfter ... ...array, // $match / $anyOf / $arrayEmpty ... ...empty, // $empty / $notEmpty ...string, // $includes / $startsWith / $endWith ... ...eq, // $eq ...ne, // $ne ...$in, // $in ...notIn, // $notIn ...boolean, // $isFalsy / $isTruly ...childCollection, };也就是说一套filter语法规则最终会被解析成对应数据库方言可执行的 SQL 条件这一设计让上层业务代码与底层数据库解耦。二、通用运算符适用于任意字段类型通用运算符对所有字段类型都有效用于最基础的等值、包含与空值判断。$eq— 等于判断字段值是否等于指定值相当于 SQL 的repository.find({ filter: { title: { $eq: 春秋, }, }, });当$eq的值为简单字符串时写法等价于title: 春秋的简写形式。从 eq.ts 的实现看$eq还有两个隐藏行为字符串字段的自动类型转换若目标字段类型是string而传入值不是字符串会执行String(val)强转传入数组时则映射为Op.inIN查询数组值支持即使不是字符串字段只要传入数组$eq也会转为IN查询。$ne— 不等于判断字段值是否不等于指定值相当于 SQL 的!repository.find({ filter: { title: { $ne: 春秋, }, }, });注意 ne.ts 中的实现细节当值为数组时映射为NOT IN当值为null时映射为IS NOT NULL即Op.ne: null而其他普通值会被翻译为{ Op.or: { Op.ne: val, Op.is: null } }—— 这意味着$ne的查询结果会包含该字段为NULL的记录因为NULL ! 值在 SQL 语义中同样成立。这在数据统计时需要特别留意。$is— 是否为指定值判断字段值是否为指定值相当于 SQL 的IS常用于空值判断repository.find({ filter: { title: { $is: null, }, }, });$not— 是否不为指定值判断字段值是否不为指定值相当于 SQL 的IS NOTrepository.find({ filter: { title: { $not: null, }, }, });$col— 字段间比较判断字段值是否等于另一个字段的值相当于 SQL 的作用于两列之间repository.find({ filter: { title: { $col: name, }, }, });以上示例表示筛选出title字段值与name字段值相等的记录适用于同一行内字段间的关联比较。$in/$notIn— 属于 / 不属于集合判断字段值是否在指定数组中分别相当于 SQL 的IN与NOT INrepository.find({ filter: { title: { $in: [春秋, 战国], }, }, }); repository.find({ filter: { title: { $notIn: [春秋, 战国], }, }, });这两个运算符在仓库中对应 in.ts 与 notIn.ts并有专门的测试用例 in.test.ts 覆盖。$empty/$notEmpty— 空值判断$empty判断一般字段是否为空。字符串字段判断是否为空串数组字段判断是否为空数组$notEmpty判断一般字段是否不为空规则同上取反。repository.find({ filter: { title: { $empty: true, }, }, }); repository.find({ filter: { title: { $notEmpty: true, }, }, });其实现位于 empty.ts会结合字段类型分别生成针对空字符串、空数组或NULL的条件。三、逻辑运算符逻辑运算符用于组合多个过滤条件是所有复杂查询的基础。$and— 逻辑与逻辑 AND相当于 SQL 的ANDrepository.find({ filter: { $and: [{ title: 诗经 }, { isbn: 1234567890 }], }, });$or— 逻辑或逻辑 OR相当于 SQL 的OR。注意其中可以嵌套其他运算符repository.find({ filter: { $or: [{ title: 诗经 }, { publishedAt: { $lt: 0000-00-00T00:00:00Z } }], }, });// 多条件 OR数组中的每个对象都是一组独立条件 repository.find({ filter: { $or: [ { status: published }, { status: draft, authorId: 1 }, ], }, });$and/$or的取值都是条件对象数组数组中的每个元素本身又是一个完整的 filter 片段因此可以任意嵌套组合出多层次的复杂查询。四、布尔类型字段运算符以下运算符专门用于布尔类型字段type: boolean在 boolean.ts 中实现。$isFalsy— 判断为假布尔字段值为false、0和NULL的情况都会被判断为$isFalsy: truerepository.find({ filter: { isPublished: { $isFalsy: true, }, }, });源码中$isFalsy在传入true/true时生成{ Op.or: { Op.is: null, Op.eq: false } }即同时覆盖NULL与false两种“假”的情况传入false时则取反生成{ Op.eq: true }。$isTruly— 判断为真布尔字段值为true和1的情况都会被判断为$isTruly: truerepository.find({ filter: { isPublished: { $isTruly: true, }, }, });相关行为可参考测试 boolean-operator.test.ts。五、数字类型字段运算符以下运算符用于数字类型字段包括type: integertype: floattype: doubletype: realtype: decimal大小比较$gt/$gte/$lt/$lte分别对应 SQL 的、、、// 大于 repository.find({ filter: { price: { $gt: 100, }, }, }); // 大于等于 repository.find({ filter: { price: { $gte: 100, }, }, }); // 小于 repository.find({ filter: { price: { $lt: 100, }, }, }); // 小于等于 repository.find({ filter: { price: { $lte: 100, }, }, });区间判断$between/$notBetween判断字段值是否在指定的两个值之间分别相当于 SQL 的BETWEEN与NOT BETWEEN。值为包含两个元素的数组[最小值, 最大值]repository.find({ filter: { price: { $between: [100, 200], }, }, }); repository.find({ filter: { price: { $notBetween: [100, 200], }, }, });六、字符串类型字段运算符以下运算符用于字符串类型字段type: string核心实现在 string.ts。包含与排除$includes/$notIncludes$includes判断字符串字段是否包含指定子串$notIncludes判断字符串字段是否不包含指定子串。repository.find({ filter: { title: { $includes: 三字经, }, }, }); repository.find({ filter: { title: { $notIncludes: 三字经, }, }, });前缀匹配$startsWith/$notStatsWith$startsWith判断字符串字段是否以指定子串开头$notStatsWith判断字符串字段是否不以指定子串开头文档中的拼写如此源码实现键名对应为$notStartsWith见 string.ts。repository.find({ filter: { title: { $startsWith: 三字经, }, }, }); repository.find({ filter: { title: { $notStatsWith: 三字经, }, }, });后缀匹配$endsWith/$notEndsWith$endsWith判断字符串字段是否以指定子串结尾$notEndsWith判断字符串字段是否不以指定子串结尾。repository.find({ filter: { title: { $endsWith: 三字经, }, }, }); repository.find({ filter: { title: { $notEndsWith: 三字经, }, }, });从源码结构看string.ts 中这两个运算符的实现键名实际写作$endWith/$notEndWith见 L159-L185使用时两种拼写都需以实际生效键名为准。SQL 风格匹配$like/$notLike/$iLike/$notILike$like字段值是否包含指定的字符串相当于 SQL 的LIKE$notLike字段值是否不包含指定的字符串相当于 SQL 的NOT LIKE$iLike字段值是否包含指定字符串且忽略大小写相当于 SQL 的ILIKE仅 PG 适用$notILike字段值是否不包含指定字符串且忽略大小写相当于 SQL 的NOT ILIKE仅 PG 适用。repository.find({ filter: { title: { $like: 计算机, }, }, }); repository.find({ filter: { title: { $notLike: 计算机, }, }, }); repository.find({ filter: { title: { $iLike: Computer, }, }, }); repository.find({ filter: { title: { $notILike: Computer, }, }, });源码细节虽然$like系列直接来源于 Sequelize 内置运算符但 NocoBase 自定义的$includes/$startsWith等运算在底层同样走 LIKE/ILIKE 路径。以 string.ts 为例PostgreSQL会通过CAST(字段 AS TEXT)将字段强制转为文本后拼接%...%通配符并区分ILIKE忽略大小写与LIKEMySQL 等数据库ILIKE会退化为LIKEMySQL 的默认排序规则本身通常不区分大小写通配符转义escapeLike()会将用户输入中的%和_转义避免通配符注入导致意外匹配MSSQL 方言下则采用[包裹的转义写法数组值支持$includes、$startsWith等传入数组时会生成多个条件并用OR/AND组合。正则匹配$regexp/$notRegexp/$iRegexp/$notIRegexp$regexp字段值是否匹配指定的正则表达式相当于 SQL 的REGEXP仅 PG 适用$notRegexp字段值是否不匹配指定的正则表达式相当于 SQL 的NOT REGEXP仅 PG 适用$iRegexp字段值是否匹配指定正则且忽略大小写相当于 SQL 的~*仅 PG 适用$notIRegexp字段值是否不匹配指定正则且忽略大小写相当于 SQL 的!~*仅 PG 适用。repository.find({ filter: { title: { $regexp: ^计算机, }, }, }); repository.find({ filter: { title: { $notRegexp: ^计算机, }, }, }); repository.find({ filter: { title: { $iRegexp: ^COMPUTER, }, }, }); repository.find({ filter: { title: { $notIRegexp: ^COMPUTER, }, }, });注意正则类运算符REGEXP、ILIKE等依赖数据库方言能力官方文档标注为仅 PostgreSQL 适用在 MySQL / SQLite 等其他数据库上使用时需要确认对应方言的等价支持。字符串运算符的完整行为可参考测试 string-operator.test.ts 与 operators/tests/string.test.ts。七、日期类型字段运算符以下运算符用于日期类型字段type: date核心实现在 date.ts。日期运算符内部会调用nocobase/utils的parseDate进行解析并根据字段类型自动处理时区与格式转换。$dateOn/$dateNotOn— 在某一天 / 不在某一天判断日期字段是否不在某天内传入日期字符串即可repository.find({ filter: { createdAt: { $dateOn: 2021-01-01, }, }, }); repository.find({ filter: { createdAt: { $dateNotOn: 2021-01-01, }, }, });源码细节$dateOn在收到2021-01-01这类单值时会解析为[当天 00:00:00, 次日 00:00:00)的左闭右开区间翻译为{ Op.gte: 起始, Op.lt: 结束 }若parseDate返回数组日期范围则同样生成区间条件。这意味着“在某一天”实际是“当天零点至次日零点之间”不会漏掉当天任意时刻的数据。$dateBefore/$dateNotBefore— 在某个值之前 / 不在之前$dateBefore判断日期字段是否在某个值之前相当于小于传入的日期值$dateNotBefore判断日期字段是否不在某个值之前相当于大于等于传入的日期值。repository.find({ filter: { createdAt: { $dateBefore: 2021-01-01T00:00:00.000Z, }, }, }); repository.find({ filter: { createdAt: { $dateNotBefore: 2021-01-01T00:00:00.000Z, }, }, });$dateAfter/$dateNotAfter— 在某个值之后 / 不在之后$dateAfter判断日期字段是否在某个值之后相当于大于传入的日期值$dateNotAfter判断日期字段是否不在某个值之后相当于小于等于传入的日期值。repository.find({ filter: { createdAt: { $dateAfter: 2021-01-01T00:00:00.000Z, }, }, }); repository.find({ filter: { createdAt: { $dateNotAfter: 2021-01-01T00:00:00.000Z, }, }, });源码细节时区与特殊日期字段date.ts中的parseDateTimezone()会根据字段类型决定解析时区——datetimeNoTz无时区时间与dateOnly仅日期字段强制按00:00解析其他日期字段使用db.options.timezone。toDate()还会针对UnixTimestampField调用field.dateToValue()将日期转回 Unix 时间戳并在转换完成后发出filterToDate事件供其他逻辑订阅。此外源码中还实现了文档未单列出的$dateBetween日期区间运算符。相关行为有大量测试覆盖如 operator/date/datetime-tz.test.ts、date-only.test.ts 与 unix-timestamp.test.ts。八、数组类型字段运算符以下运算符用于数组类型字段type: array核心实现在 array.ts。这类运算符对不同数据库生成了差异化的 SQL运算符语义PostgreSQLMySQLSQLite$match数组值完全匹配指定数组与双向包含JSON_CONTAINS双向json()相等比较$anyOf包含指定数组中任意值?|运算符JSON_OVERLAPSjson_each子查询$noneOf不包含指定数组中任意值取反?|NOT JSON_OVERLAPS取反json_each$arrayEmpty数组是否为空jsonb_array_lengthjson_lengthjson_array_length$arrayNotEmpty数组是否不为空同上取0同上取0同上取0$match/$notMatch— 完全匹配 / 不匹配判断数组字段的值是否不匹配指定数组中的值repository.find({ filter: { tags: { $match: [文学, 历史], }, }, }); repository.find({ filter: { tags: { $notMatch: [文学, 历史], }, }, });$anyOf/$noneOf— 包含任意值 / 不包含任意值判断数组字段的值是否不包含指定数组中的任意值repository.find({ filter: { tags: { $anyOf: [文学, 历史], }, }, }); repository.find({ filter: { tags: { $noneOf: [文学, 历史], }, }, });$arrayEmpty/$arrayNotEmpty— 数组是否为空repository.find({ filter: { tags: { $arrayEmpty: true, }, }, }); repository.find({ filter: { tags: { $arrayNotEmpty: true, }, }, });源码细节emptyQuery()会生成IFNULL(json_array_length(字段), 0) 0PG 用coalesce(jsonb_array_length(...), 0)MySQL 用json_length(...)这样的表达式来判断空数组并对NULL值做了兜底处理因此$arrayEmpty也能覆盖字段为NULL的情况。九、关系字段类型运算符以下运算符用于判断关系是否存在字段类型包括type: hasOnetype: hasManytype: belongsTotype: belongsToMany实现在 association.ts$exists— 有关系数据repository.find({ filter: { author: { $exists: true, }, }, });源码中直接映射为{ Op.not: null }即关系外键不为空。$notExists— 无关系数据repository.find({ filter: { author: { $notExists: true, }, }, });源码中映射为{ Op.is: null }即关系外键为空。例如筛选出没有关联作者author为空的图书记录。十、扩展自定义运算符db.registerOperators()NocoBase 的运算符体系是开放可扩展的。Database类提供了registerOperators()方法用于注册自定义运算符// packages/core/database/src/database.ts#L764-L768 registerOperators(operators: MapOfOperatorFunc) { for (const [key, operator] of Object.entries(operators)) { this.operators.set(key, operator); } }每个运算符本质上是一个函数接收(value, ctx)两个参数返回 Sequelize 可识别的查询条件对象Op符号组合、Sequelize.literal原生 SQL 片段等。ctx中携带了字段路径、模型、数据库实例等上下文信息如 string.ts 中的ctx.db.sequelize.getDialect()、ctx.fullName、ctx.fieldName。扩展示例——注册一个判断字符串长度为指定值的运算符import { Op } from sequelize; db.registerOperators({ $lengthOf(value) { // 返回 Sequelize 查询条件此处为示意实际需结合具体数据库方言实现 return { [Op.like]: ${_.repeat(value)}, }; }, }); // 使用 repository.find({ filter: { title: { $lengthOf: 4, }, }, });// 自定义运算符还可以利用 ctx 访问数据库方言实现跨库逻辑 db.registerOperators({ $customOperator(value, ctx) { const dialect ctx.db.sequelize.getDialect(); // ...根据 dialect 返回不同实现 }, });扩展点的完整实现可查阅 database.ts 以及 operators/index.ts 中内置运算符的聚合方式二者共同构成了“内置兜底 自定义覆盖”的可插拔运算符机制。十一、与其他查询 API 的组合使用filter参数同样适用于findOne、findAndCount、count等 Repository 方法// findOne查询第一条匹配记录 const book await repository.findOne({ filter: { title: { $eq: 春秋, }, }, }); // findAndCount同时返回数据与总数常用于分页列表 const [rows, total] await repository.findAndCount({ filter: { $and: [ { price: { $between: [50, 200] } }, { publishedAt: { $dateOn: 2021-01-01 } }, ], }, offset: 0, limit: 20, }); // count仅统计数量 const count await repository.count({ filter: { isPublished: { $isTruly: true, }, }, });这些查询方法的行为在 repository.test.ts、filter.test.ts 与 filter-match.ts 中有大量测试与实现可对照验证。十二、小结运算符速查表分类运算符相当于 SQL适用字段通用$eq/$ne/!任意通用$is/$notIS/IS NOT任意常配null通用$col列与列比较任意通用$in/$notInIN/NOT IN任意通用$empty/$notEmpty空值/空串/空数组判断任意逻辑$and/$orAND/OR条件组合布尔$isFalsy/$isTruly真假判断boolean数字$gt/$gte/$lt/$lte///数字数字$between/$notBetweenBETWEEN/NOT BETWEEN数字字符串$includes/$notIncludes包含/不包含子串string字符串$startsWith/$notStatsWith前缀/非前缀string字符串$endsWith/$notEndsWith后缀/非后缀string字符串$like/$notLikeLIKE/NOT LIKEstring字符串$iLike/$notILikeILIKE/NOT ILIKE仅 PGstring字符串$regexp/$notRegexp/$iRegexp/$notIRegexpREGEXP/~*等仅 PGstring日期$dateOn/$dateNotOn当天/非当天date日期$dateBefore/$dateNotBefore/date日期$dateAfter/$dateNotAfter/date数组$match/$notMatch/$anyOf/$noneOf数组包含匹配array数组$arrayEmpty/$arrayNotEmpty空/非空数组array关系$exists/$notExists外键非空/为空关系字段掌握这五大类 40 运算符及其底层实现逻辑你就能在 NocoBase 中构建从简单等值查询到跨字段、跨关系、跨数据库方言的任意过滤条件并在需要时通过registerOperators()无缝扩展自己的查询能力。【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表