
数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载子查询Subquery是复杂 SQL 查询中不可或缺的能力而 Objection.js 作为 SQL-friendly 的 Node.js ORM将子查询的编写方式与 Knex 完全对齐在几乎所有接受值的地方传入一个函数、一个 QueryBuilder 实例甚至一段 Knex 查询即可生成嵌套的 SQL。本篇技术指南以仓库中的官方配方文档 doc/recipes/subqueries.md 为核心结合 objection.js 的模块导出与底层实现源码系统讲解函数式子查询、构建器式子查询、通过ref关联父查询列以及用relatedQuery复用模型关系完成子查询的完整套路。读完本文你将能在 Objection.js 中自由编写与平均年龄比较统计每人的宠物数量等典型子查询场景并理解它们如何在底层被翻译为安全的 Knex SQL。子查询的本质在值的位置传入可执行的东西在 Knex 中子查询的标准写法是在查询构建方法如where、select、from的参数位置传一个函数Knex 会把这个函数当作子查询构建器调用。Objection.js 完整继承了这一约定——文档原文明确指出Subqueries can be written just like in knex: by passing a function in place of a value. A bunch of query building methods accept a function.也就是说凡是where、select、having、from等接受值的查询构建方法几乎都可以接收函数来生成子查询。函数形式之外Objection.js 还做了两处增强可以直接传入Objection.js 的QueryBuilder实例即Person.query()返回的构建器可以直接传入Knex 查询构建器实例。这三种形式最终都会被 Objection.js 的底层查询转换逻辑识别为子查询并嵌入外层 SQL。从模块出口看lib/objection.js 对外导出了Model、QueryBuilder、ref、raw、val、fn等核心构件其中QueryBuilder就是子查询可以直接作为值传递的基础。写法一传入函数最接近 Knex 的写法当一个查询需要引用外层表的数据做比较运算时函数式子查询是最直观的写法。官方文档给出的经典案例是查询年龄大于平均年龄的人const peopleOlderThanAverage await Person.query().where( age, , builder { builder.avg(age).from(persons); } ); console.log(peopleOlderThanAverage);这里的builder是一个全新的查询构建器builder.avg(age).from(persons)会被编译成(select avg(age) from persons)子查询再与外层的where age ...组合。要点如下函数内不能再调用.query()builder本身就是构建器直接链式调用avg、select、from等方法即可几乎任何值位置都可用where、whereIn、select、having、from、join的on条件等凡是 Knex 文档中说明接受函数的位置Objection.js 都保持同样的行为这是标准 Knex 语义因此如果你熟悉 Knex 官方文档 中子查询的用法可以直接照搬。写法二传入 QueryBuilder 实例Objection.js 的特色函数写法之外Objection.js 允许直接把一个完整的QueryBuilder实例作为值传入。同一个年龄大于平均年龄的例子可以改写为const peopleOlderThanAverage await Person.query().where( age, , Person.query().avg(age) ); console.log(peopleOlderThanAverage);Person.query().avg(age)返回的是一个QueryBuilder实例它被直接嵌入了外层where。这种方式相比函数写法有两大优势可复用同一个构建器可以被变量保存、按条件拼接后再传入逻辑更灵活类型更统一构建器实例可以在任何子查询位置复用包括作为raw的绑定值下文详述。官方文档同样确认Knex 查询构建器实例也可以作为值传入即knex(persons).avg(age)这类实例同样可用。从源码看Objection.js 的查询转换器如 operations/ObjectionToKnexConvertingOperation.js会在构建最终 SQL 时把嵌入的 QueryBuilder 实例递归转换为 Knex 查询从而实现子查询的嵌套编译。写法三用 ref 引用父查询的列子查询与父查询之间的关联是高频需求。Objection.js 提供了ref工厂函数返回一个 ReferenceBuilder 实例专门用于引用表、列、JSON 字段并支持类型转换castInt()、castText()等与别名.as()。官方文档的示例是统计每个人的宠物数量通过ref(persons.id)在子查询中引用父查询的列const { ref } require(objection); const peopleWithPetCount await Person.query().select([ persons.*, Pet.query() .where(ownerId, ref(persons.id)) .count() .as(petCount) ]); console.log(peopleWithPetCount[4].petCount);这段查询等价于 SQLselect persons.*, ( select count(*) from pets where ownerId persons.id ) as petCount from personsref 的底层实现从 lib/queryBuilder/ReferenceBuilder.js 源码可以看到ref的工作机制ref(reference)工厂函数如果传入的已经是ReferenceBuilder实例则原样返回否则new ReferenceBuilder(reference)见 ReferenceBuilder.js#L241-L247引用表达式会通过parseFieldExpression解析为表名 列名 JSON 路径访问结构_parseExpression因此ref(persons.id)可以准确拆出表persons与列id编译时通过_createReferenceSql生成??占位符并把完整列名放入绑定数组ReferenceBuilder.js#L172-L193列名以绑定参数形式传给数据库引擎天然规避 SQL 注入风险支持from(table)/table(table)显式指定表名、model(modelClass)绑定模型类此时fullColumn会通过builder.tableRefFor(modelClass)自动解析表引用以及castTo(sqlType)、castInt()、castText()、castJson()、as(alias)等链式方法。这意味着ref不仅能在where比较中引用父查询列还能引用 JSON 字段如ref(persons.profile:age)、做显式类型转换后参与比较这些扩展用法在 doc/api/objection/README.md 的ref章节有完整示例。关联父查询的另一条路径relatedQuery上面的宠物数量例子还可以利用Person上已定义的pets关系用静态方法relatedQuery一步完成由 Objection.js 自动生成关联子查询const peopleWithPetCount await Person.query().select([ persons.*, Person.relatedQuery(pets) .count() .as(petCount) ]); console.log(peopleWithPetCount[4].petCount);relatedQuery的完整签名与用法见 doc/api/model/static-methods.md。其底层实现位于 lib/model/Model.js#L770-L789relatedQuery会先通过modelClass.getRelation(relationName)拿到关系对象再在相关模型类的查询构建器上注册一个findOperationFactory关键逻辑在于const isSubQuery !builder.for(); const owner isSubQuery ? RelationOwner.createParentReference(builder, relation) : RelationOwner.create(builder.for());即当构建器没有指定for()父行时它会被判定为子查询场景Objection.js 自动为它创建父引用createParentReference从而在生成的 SQL 中把相关表的外键列与外层查询的主键列正确关联起来无需手写ref(persons.id)。这正好解释了为什么Person.relatedQuery(pets).count()能直接作为select中的子查询使用——你只声明关系名外键连接条件由框架从关系定义中推导。与 raw 组合子查询作为绑定值除了where与select的常规位置子查询还可以作为 raw 的绑定参数嵌入任意 SQL 片段。这也是官方配方文档 doc/recipes/raw-queries.md 与子查询主题的交叉点。例如把子查询放进array(...)构造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)这里的绑定值是Person.query()产生的 QueryBuilderObjection.js 的raw会自动识别并把它编译成嵌套子查询。同时raw的绑定参数还支持嵌套raw、ref、val以及 Knex 查询官方raw文档中有raw(coalesce(:sumQuery, 0) as :alias:, { sumQuery: Person.query().sum(age) })这样的命名占位符嵌套示例可参见 doc/api/objection/README.md 的raw章节。源码佐证子查询在测试中的真实用法仓库的集成测试 tests/integration/find.js 大量使用了ref引用与子查询组合例如用ref(m1.model2_prop2)在where中关联两张表tests/integration/find.js#L296以及Model1.query().select(id)直接作为raw的绑定值tests/integration/find.js#L1214。TypeScript 测试目录 tests/ts/query-builder-api/find-methods.ts 中也能看到avg(age)子查询的链式用法。这些测试用例一方面验证了函数、QueryBuilder、ref三种形式都能正确编译执行另一方面也为你手写子查询提供了可直接参考的模式。小结与实践建议场景推荐写法特点与外层列无关的聚合子查询如大于平均值where(age, , builder {...})或Person.query().avg(age)最贴近 Knex 语义两种形式等价子查询需要引用父查询列ref(persons.id)列名以绑定参数传递安全且支持 JSON 字段与类型转换关系已定义、想少写外键条件Person.relatedQuery(pets).count().as(...)自动生成父引用与外键关联自定义 SQL 片段中嵌入子查询raw(array(?) as childIds, [Person.query()...])绑定值支持 QueryBuilder、ref、raw、val 的任意嵌套最后三点提醒函数形式与构建器形式可自由混用两者编译结果一致按可读性取舍即可ref引用父查询时务必带上表名如ref(persons.id)尤其在select别名、多表 join 场景下否则可能因列名歧义产生错误的 SQL相关模型类需要已绑定 KnexrelatedQuery、Person.query()才能在子查询中正确生成表名模型的 Knex 绑定方式参见 doc/guide/models.md 与仓库示例 examples/minimal/app.js。赞分享数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载相关推荐Objection.js QueryBuilder 其他实用方法完全指南上下文、钩子、分页与查询内省Objection.js QueryBuilder 其他实用方法完全指南上下文、钩子、分页与查询内省 Objection.js 的 QueryBuilder数据库后端Security-101 网络安全入门CIA 三元组与真实性、不可否认性、隐私核心概念精讲Security 101 网络安全入门CIA 三元组与真实性、不可否认性、隐私核心概念精讲 本篇技术指南源自本仓库Security 1018 课网络安全入数据库后端使用broom进行生存分析coxph模型结果的tidy处理使用broom进行生存分析coxph模型结果的tidy处理 在生物医学研究和流行病学分析中生存分析是评估时间相关事件如疾病进展、死亡或复发的重要统计方法上一篇Gutenberg PluginPostPublishPanel 指南在发布后面板注入自定义内容的 Slot Fill 实战下一篇将树莓派土壤湿度传感器接入 Azure IoT Hub从 MQTT 迁移到云端的完整实战指南IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考