尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Objection.js 原始 SQL 查询(Raw Queries)实战指南:raw / ref / val / fn 与 knex.raw 完全解析

发布时间:2026/9/29 5:57:08

资讯中心
01
ARTICLE

Objection.js 原始 SQL 查询(Raw Queries)实战指南:raw / ref / val / fn 与 knex.raw 完全解析

Objection.js 原始 SQL 查询(Raw Queries)实战指南:raw / ref / val / fn 与 knex.raw 完全解析
数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载本文围绕 Objection.js一个 SQL 友好的 Node.js ORM官方配方文档 doc/recipes/raw-queries.md 展开系统讲解如何在 Objection 查询中混入原生 SQL 片段、如何用raw/ref/val/fn四种构建器安全组合 SQL、以及何时应改用knex.raw执行完全自定义查询。读完本文你将掌握占位符体系?/??/ 命名占位符、嵌套绑定技巧和底层类型转换原理能够在真实项目中游刃有余地处理「ORM 写不出的 SQL」。一、先厘清两个概念混入 SQL 与完全自定义查询官方文档开门见山给出了两个核心场景的边界混入 SQL在 Objection 的查询链如Person.query()内部把一段原生 SQL 片段作为参数传给.select()、.where()、.patch()等方法。此时应使用主模块导出的raw函数完全自定义查询脱离模型与查询链直接向数据库发送一段独立 SQL如SELECT 1、DDL 语句。此时应使用knex.raw()。二者的区分依据在于查询的骨架归谁掌控骨架是 Objection 查询构建器、只是局部需要原生 SQL就用raw骨架本身就是要手写的整段 SQL就用knex.raw。raw函数的行为与 Knex 的 raw 方法一致但有一个关键增强它额外支持 Objection 自身的类型系统——raw、ref、val、Objection 查询构建器QueryBuilder等都可以作为绑定参数被安全解析。这一点从 lib/objection.js 的导出可以看到主模块同时导出了raw、ref、val、fn四个工厂函数const { ref } require(./queryBuilder/ReferenceBuilder); const { val } require(./queryBuilder/ValueBuilder); const { raw } require(./queryBuilder/RawBuilder); const { fn } require(./queryBuilder/FunctionBuilder);二、raw() 函数入门从签名到内部实现2.1 基本用法const { raw } require(objection); const ageToAdd 10; await Person.query().patch({ age: raw(age ?, ageToAdd), });这条语句生成的 SQL 相当于UPDATE person SET age age 10具体表名取决于Person的映射配置。ageToAdd通过?占位符绑定不会被直接拼接进 SQL 字符串从而避免注入风险。2.2 源码层面的实现lib/queryBuilder/RawBuilder.js 中的实现非常简洁整个构建器只有三个关键成员_sqlSQL 模板字符串_args绑定参数数组_as可选的别名通过.as()设置。raw(...)工厂函数RawBuilder.js#L81-L84先调用normalizeRawArgs归一化参数——若调用形式为raw(sql, [a, b])第二个参数是数组会把数组展开为参数列表否则按raw(sql, a, b)逐个收集function normalizeRawArgs(argsIn) { const [sql, ...restArgs] argsIn; if (restArgs.length 1 Array.isArray(restArgs[0])) { return { sql, args: restArgs[0] }; } else { return { sql, args: restArgs }; } }也就是说raw(age ?, [10])与raw(age ?, 10)两种写法等价。实例会被打上不可枚举的标记isObjectionRawBuilder供下游转换逻辑识别。RawBuilder的toKnexRaw(builder)方法RawBuilder.js#L22-L43在查询真正执行时才惰性地把自身转换为 Knex raw 实例如果设置了别名还会在 SQL 尾部追加as ??或as :__alias__:。这正是 doc/api/objection/README.md#raw 中RawBuilder 是 knex raw 的包装不直接依赖 knex实例在查询执行时惰性转换这一描述的落地实现。三、占位符体系? / ?? 与命名占位符官方 API 文档对占位符给出了明确约定这是安全使用 raw 的前提占位符用途示例?值value绑定raw(age ?, 10)??标识符identifier绑定如列名、别名raw(?? 1, age):someName:命名标识符绑定raw(coalesce(:sumColumn:, 0), { sumColumn: age }):someName命名值绑定raw(:value1 :value2, { value1: 50, value2: 25 })官方文档特别强调在 raw SQL 片段中应始终用占位符代替直接拼接用户输入。占位符会被发送给数据库引擎由引擎负责安全的插值这是防止 SQL 注入的第一道防线。命名占位符的完整示例来自 doc/api/objection/README.md#rawawait Person.query() .select( raw(coalesce(sum(:sumColumn:), 0) as :alias:, { sumColumn: age, alias: ageSum }) ) .where( age, , raw(:value1 :value2, { value1: 50, value2: 25 }) );当raw的第二个参数是一个普通对象时toKnexRaw会走对象分支RawBuilder.js#L26-L32把对象键作为命名占位符解析。注意:sumColumn:带后缀冒号解析为标识符、:value1无后缀冒号解析为值规则与 Knex 一致。四、在 Objection 查询中混用 raw 的实战场景4.1 聚合 条件 排序的组合查询官方文档的第二个示例把raw用在了select、where、orderBy三个位置const { raw } require(objection); const childAgeSums await Person.query() .select(raw(coalesce(sum(??), 0), age).as(childAgeSum)) .where( raw(?? || || ??, firstName, lastName), Arnold Schwarzenegger ) .orderBy(raw(random())); console.log(childAgeSums[0].childAgeSum);逐个拆解.select(raw(coalesce(sum(??), 0), age).as(childAgeSum))??绑定列名age.as(childAgeSum)设置结果列别名.where(raw(?? || || ??, firstName, lastName), Arnold Schwarzenegger)用数据库方言的字符串拼接运算符PostgreSQL 的||把firstName与lastName拼成一个完整姓名再做等值比较规避了跨方言拼接函数差异.orderBy(raw(random()))调用数据库原生随机函数PostgreSQL 为random()MySQL 需换为rand()实现随机排序。这里的.as()是RawBuilder提供的链式方法RawBuilder.js#L17-L20在toKnexRaw时被转换进 SQL 的as子句。4.2 更新patch / update中的 SQL 表达式除了selectraw也常用于写入场景。除了开篇的age ?示例测试套件 tests/integration/find.js 中还有大量raw与模型实例方法组合的用例例如把raw直接用作更新值model2_prop2: raw(10 10)见 tests/integration/find.js#L348以及在where中嵌套子查询tests/integration/find.js#L357。这印证了raw可以在查询链的任意取值位置使用。五、fn() 辅助函数声明式调用 SQL 函数官方文档指出前面的聚合示例还有一个完全等价的fn写法const { fn, ref } require(objection); const childAgeSums await Person.query() .select(fn.coalesce(fn.sum(ref(age)), 0).as(childAgeSum)) .where( fn.concat(ref(firstName), , ref(lastName)), Arnold Schwarzenegger ) .orderBy(fn(random)); console.log(childAgeSums[0].childAgeSum);fn的底层实现在 lib/queryBuilder/FunctionBuilder.jsFunctionBuilder直接继承自RawBuilderfn(...)把 SQL 模板拼成函数名(?, ?, ...)的形式参数全部走绑定function fn(...argsIn) { const { sql, args } normalizeRawArgs(argsIn); return new FunctionBuilder(${sql}(${args.map(() ?).join(, )}), args); }内置了一批常用函数快捷方式FunctionBuilder.js#L13-L15coalesce、concat、sum、avg、min、max、count、upper、lower另有fn.now(precision)生成CURRENT_TIMESTAMP(precision)默认精度 6FunctionBuilder.js#L17-L28。使用fn的好处是参数天然绑定、无需手写?占位符且返回的对象同样是RawBuilder子类因此.as()、嵌套、作为绑定参数等能力完全一致。六、ref() 与 val()引用构建器与值构建器raw之外Objection 还提供两个配套构建器它们同样可以作为绑定参数混入 raw SQL6.1 ref()安全的列引用ref工厂函数返回ReferenceBuilderlib/queryBuilder/ReferenceBuilder.js用于引用表、列、JSON 字段并支持类型转换与别名。典型用法来自 doc/api/objection/README.md#refconst { ref } require(objection); await Model.query() .select([ id, ref(Model.jsonColumn:details.name).castText().as(name), ref(Model.jsonColumn:details.age).castInt().as(age) ]) .where(age, , ref(OtherModel.ageLimit));ref支持castText()/castInt()/castBigInt()/castFloat()/castDecimal()/castReal()/castBool()/castJson()等链式转换方法ReferenceBuilder.js#L89-L125底层会生成CAST(... AS type)。在withGraphJoined/joinRelated场景下冒号既可能表示 JSON 字段路径又可能表示关联路径此时可用.from(children:children)显式指定表见 API 文档的歧义说明。当ref与??标识符占位符结合时还能处理 JSON 字段提取??#{field1,field2}ReferenceBuilder.js#L183-L193。6.2 val()显式的值类型控制val工厂函数返回ValueBuilderlib/queryBuilder/ValueBuilder.js用于明确值的类型。默认情况下对象和数组会被序列化为 JSON_toJson isObject(value)见 ValueBuilder.js#L11并可通过.asArray()、.castTo(real[])、.castJson()等控制输出。官方 API 文档示例const { val, ref } require(objection); // 比较 JSON 对象 await Model.query().where( ref(Model.jsonColumn:details), , val({ name: Jennifer, age: 29 }) ); // 插入数组 await Model.query().insert({ numbers: val([1, 2, 3]).asArray().castTo(real[]) });ValueBuilder的_createRawArgsValueBuilder.js#L75-L101展示了三种输出形态JSON 字符串绑定?、数组展开为ARRAY[?, ?, ...]、普通值绑定?并可附加CAST与as ??别名。七、嵌套绑定raw 里还能再放 raw、ref、val 和子查询官方文档强调绑定参数可以是其他 raw 实例、QueryBuilder或几乎任何你能想到的东西。这背后依赖 lib/utils/buildUtils.js 的buildArg分派逻辑对象带有toKnexRaw方法RawBuilder、ReferenceBuilder、ValueBuilder、FunctionBuilder均实现→ 递归调用其toKnexRaw(builder)转换为 Knex raw对象是 Objection QueryBuilderisObjectionQueryBuilderBase true→ 作为子查询转换为 Knex 查询其余值原样返回。7.1 子查询作为绑定参数官方文档的数组子查询示例此处对原文档做了括号闭合修正原示例.select(...)缺少右括号const { raw, ref } require(objection); const people await Person .query() .alias(p) .select(raw(array(?) as childIds, [ Person.query() .select(id) .where(id, ref(p.parentId)) ])); console.log(child identifiers:, people[0].childIds)这里把一个 Objection 子查询按ref(p.parentId)关联父表别名p的 id 查询作为array(...)的参数绑定进去最终为每个人物聚合出其子记录的 id 数组。注意?绑定的是查询构建器整体而非拼接字符串子查询在构建时被转换为 Knex 查询并作为绑定值嵌入buildArg中调用arg.subqueryOf(builder).toKnexQuery()。7.2 raw / ref / val / knex.raw 混用来自 doc/api/objection/README.md#raw 的官方嵌套示例const { val } require(objection); await Person .query() .select(raw(coalesce(:sumQuery, 0) as :alias:, { sumQuery: Person.query().sum(age), alias: ageSum })) .where(age, , raw(:value1 :value2, { value1: val(50), value2: knex.raw(25) }));其中sumQuery绑定的是 Objection 子查询、value1绑定的是val(50)、value2绑定的是原生knex.raw(25)——三种不同来源的参数被同时塞进一个raw调用全部被正确解析。八、完全自定义查询knex.raw当查询骨架完全脱离 Objection 模型时官方文档给出的做法是回到 Knex 层面const knex Person.knex(); await knex.raw(SELECT 1);Person.knex()返回该模型绑定的 Knex 实例knex.raw可以执行任意 SQL。这类场景包括DDL、存储过程调用、临时表操作、跨库 join 等不适合放进模型查询链的语句。在集成测试中也常见这种用法例如 tests/integration/crossDb/mysql.js 用session.knex.raw(CREATE DATABASE ...)创建测试数据库。knex 自身的 raw 同样支持?/??/ 命名占位符其参数可以是 Objection 类型在混用场景中会被 Knex 的绑定机制处理但要注意一旦脱离 Objection 查询链ref/val/fn的自动转换能力就不再可用需要手动展开。九、*Raw 系列 Helper 方法除了raw()函数QueryBuilder 还内置了一批以Raw结尾的便捷方法用于在对应子句直接插入原生 SQL全部定义于 lib/queryBuilder/QueryBuilderBase.js方法位置对应源码whereRaw(...)WHERE 子句QueryBuilderBase.js#L204-L205joinRaw(...)JOIN 子句QueryBuilderBase.js#L144-L145groupByRaw(...)GROUP BY 子句QueryBuilderBase.js#L324-L325orderByRaw(...)ORDER BY 子句QueryBuilderBase.js#L332-L333havingRaw(...)HAVING 子句QueryBuilderBase.js#L432-L433这些方法内部通过KnexOperation把参数原样转发给 Knex 的对应方法同样接受?/??占位符。完整的方法清单与签名可参考 doc/api/query-builder/find-methods.md#whereraw 等文档。例如官方文档提到的whereRaw可写作await Person.query() .whereRaw(?? || ? ?, [firstName, , Arnold Schwarzenegger]);当原生 SQL 只占某一个子句时这些*Raw方法比在.where()中塞raw()更直白而当原生 SQL 需要与ref/val/ 子查询等 Objection 类型组合时raw()函数更具表现力。十、底层原理Objection 类型如何被转换为 Knex理解raw生态的关键在于类型转换链路。所有接受这些构建器的查询操作最终都经过 lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js其类注释明确写道它将 Objection 类型转换为 Knex 类型例如 Objection 查询构建器转换为 Knex 查询构建器、Objection RawBuilder 转换为 Knex Raw 实例。convertArgsObjectionToKnexConvertingOperation.js#L54-L74按优先级分派带toKnexRaw方法的对象 → 调用其转换Objection QueryBuilder → 转换为子查询数组 / 函数 / 模型实例 / 普通对象 → 各自递归转换其他值 → 原样返回。同时它还承担skipUndefined的校验当某个参数为undefined时若未调用.skipUndefined()会抛出明确错误ObjectionToKnexConvertingOperation.js#L34-L52。这一设计保证了raw中嵌套的任何 Objection 类型都能在查询构建时被惰性、递归地展开为合法的 Knex raw 绑定。十一、最佳实践与安全注意综合官方文档与源码使用 raw 查询时建议遵循以下原则占位符优先所有用户输入值、列名一律通过?/??或命名占位符绑定绝不直接字符串拼接。这是 doc/api/objection/README.md#raw 反复强调的注入防护手段按场景选工具局部混入用raw()/*Raw方法整段独立 SQL 用knex.raw()涉及列引用、JSON 字段、类型转换时优先ref()与val()纯 SQL 函数调用可考虑fn()声明式写法留意方言差异raw(random())、?? || || ??这类语法与数据库方言强相关PostgreSQL 支持||与random()MySQL 则是CONCAT与rand()。从 examples/ 与 docker-compose.yml 可见本项目同时面向多数据库跨库场景请使用fn.concat等抽象能力或条件分支 SQL嵌套要克制raw支持任意深度嵌套raw 套 raw 套子查询但过度嵌套会显著降低可读性建议拆分变量后再组合配合测试验证仓库的集成测试如 tests/integration/find.js提供了大量 raw 与模型方法组合的参考用例动手前可先查阅确认目标方言下的 SQL 语义。掌握这套工具链后Objection 的查询能力将与原生 SQL 完全对齐模型层负责结构化与类型安全raw/ref/val/fn负责在需要处无缝注入原生能力knex.raw负责兜底任意自定义 SQL——这正是官方将其作为独立配方文档收录的价值所在。赞分享数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载相关推荐IoT-For-Beginners 入门篇从零构建你的第一个云端 IoT 夜灯项目IoT For Beginners 入门篇从零构建你的第一个云端 IoT 夜灯项目 本篇技术指南围绕开源课程 IoT For Beginners https:数据库后端objection.js 模块导出一览Model、initialize、transaction、ref、raw、val、fn 与常用工具函数实战指南objection.js 模块导出一览Model、initialize、transaction、ref、raw、val、fn 与常用工具函数实战指南 本篇技术数据库后端MikroORM 原生 SQL 查询片段完全指南raw()、sql 标签模板与参数绑定实战MikroORM 原生 SQL 查询片段完全指南raw 、sql 标签模板与参数绑定实战 本篇技术指南以 MikroORM 的原生 SQL 查询片段raw后端上一篇DeepTutor终极指南打造您的个人AI学习助手下一篇SoccerData终极指南8大足球数据源一站式抓取与分析工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。