ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Actual 的 ActualQL 查询语言完全指南:从基础查询到拆分交易与操作符实战

Actual 的 ActualQL 查询语言完全指南:从基础查询到拆分交易与操作符实战 Actual 的 ActualQL 查询语言完全指南从基础查询到拆分交易与操作符实战【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actualActualQL 是 Actual本地优先的个人财务管理应用在 0.0.129 版本中引入的查询语言用于替代此前行为固化在服务端的filterTransactions方法让用户能够以声明式语法自由查询交易、排序、筛选并聚合数据。本文以 ActualQL 官方文档为主线结合仓库源码查询构建器、编译器与测试用例逐层展开帮助你掌握构建查询、执行查询、操作符筛选以及拆分交易split transactions处理的完整实战方案。一、ActualQL 是什么从filterTransactions到可组合查询在 ActualQL 出现之前Actual 仅提供filterTransactions这类内置方法搜索交易但其行为完全被硬编码在后端你无法自定义排序规则、无法针对特定字段精确搜索、也无法直接对金额求和。ActualQL 提供了一个轻量级的查询语法把上述能力全部开放给调用方。一个最基础的 ActualQL 查询长这样q(transactions) .filter({ category.name: Food, date: 2021-02-20, }) .select([id, date, amount]);该查询会返回2021-02-20当天、类别为Food的所有交易的id、date、amount字段。值得强调的是Actual 自身的大部分功能都在使用 ActualQL文档原话为 Most of Actual uses ActualQL因此通过 API 你能访问到与 Actual 应用内部完全一致的查询能力而不是一套受限的简化接口。二、快速上手构建查询与执行查询ActualQL 的用法分为两步先用q()构建查询对象再用runQuery()执行它。let { q, runQuery } require(actual-app/api); let { data } await runQuery(q(transactions).select(*));执行结果是一个对象其中data属性保存查询结果。上面的例子中data是系统中全部交易的数组。从源码看执行链路在 packages/api/methods.ts 中可以看到runQuery与aqlQuery都是把查询序列化后通过消息通道发送给核心引擎export function runQuery(query: Query) { return send(api/query, { query: query.serialize() }); } export function aqlQuery(query: Query) { return send(api/query, { query: query.serialize() }); }注意当前版本中runQuery已被标记为deprecated源码注释建议改用aqlQueryPlease useaqlQueryinstead. This function will be removed in a future release.。文档示例仍以runQuery演示两者行为一致新代码建议直接使用aqlQuery。在引擎侧请求最终进入 packages/loot-core/src/server/aql/index.ts 的aqlQuery它将查询状态交给编译与执行管线compileAndRunAqlQuery结合 schema 与执行器schemaExecutors完成从查询对象到结果集的转换。q构建器与 Query 类q是一个工厂函数返回Query实例其完整定义位于 packages/loot-core/src/shared/query.ts 与 API 侧的 packages/api/app/query.ts。Query采用不可变链式风格每次调用都返回携带新状态的新实例。除了文档提到的filter、select、options它还提供了大量可用方法方法作用filter(expr)追加筛选条件unfilter(keys?)按字段名移除指定筛选条件不传参数则清空全部select(exprs)指定返回字段支持*或字段数组calculate(expr)执行聚合计算如求和结果作为result返回groupBy(exprs)按表达式分组orderBy(exprs)排序limit(n)/offset(n)分页options(opts)设置表级选项如拆分交易处理raw()原始模式跳过字段映射withDead()包含已删除tombstone记录withoutValidatedRefs()关闭引用字段校验serialize()序列化为可传输的查询状态在 packages/api/app/query.ts 可以看到QueryState的默认初始化tableOptions、filterExpressions、selectExpressions、groupExpressions、orderExpressions默认为空validateRefs默认为true即默认会校验字段引用是否存在于 schema 中。三、搜索交易filter 与操作符详解调用filter即对查询施加条件只有满足所有条件的记录才会被返回。filter 对象的键是字段名值是条件默认执行等于比较也支持传入各种操作符。基础示例多字段 比较操作符q(transactions) .filter({ category.name: Food, date: { $gte: 2021-01-01 }, }) .select(*);date: { $gte: 2021-01-01 }表示返回2021-01-01及之后的交易。完整操作符列表文档明确列出的可用操作符为$eq、$lt、$lte、$gt、$gte、$ne、$oneof、$regex、$like、$notlike。操作符含义$eq等于默认$lt/$lte小于 / 小于等于$gt/$gte大于 / 大于等于$ne不等于$oneof值属于给定集合中的任意一个$regex正则表达式匹配$like模糊匹配SQL LIKE 风格$notlike模糊不匹配在编译器 packages/loot-core/src/server/aql/compiler.ts 中可以看到这些操作符的底层 SQL 实现$oneof被编译为IN (...)子句且会自动对 id 集合去重$like使用UNICODE_LIKENORMALISE实现大小写无关的模糊匹配模式串同样会被规范化$regex在编译器源码中对应分支名为$regexp编译为REGEXP(...)$notlike编译为NOT UNICODE_LIKE(...) OR field IS NULL未识别的操作符会抛出CompileError: Unknown operator。这意味着文档描述之外你还拥有正则与 LIKE 通配符等灵活的字符串匹配能力。数组条件自动合并为 AND如果给某个字段传入数组多个条件会被自动用$and组合q(transactions) .filter({ date: [{ $gte: 2021-01-01 }, { $lte: 2021-12-31 }], }) .select(*);这等价于显式使用$andq(transactions) .filter({ $and: [{ date: { $gte: 2021-01-01 } }, { date: { $lte: 2021-12-31 } }], }) .select(*);两条查询都限定交易日期在2021-01-01与2021-12-31之间。$and 与 $or组合多条独立条件$and与$or接收条件数组并合并多个条件。例如获取多个日期的交易q(transactions) .filter({ $or: [{ date: 2021-01-01 }, { date: 2021-01-02 }], }) .select(*);上述查询会返回2021-01-01或2021-01-02的交易。字段与点路径dotted path文档示例中的category.name是一种点路径字段引用它沿外键关系穿透到关联表。在 schema 定义 packages/loot-core/src/server/aql/schema/index.ts 中transactions表的category字段被声明为f(id, { ref: categories })因此可以直接用category.name引用类别的名称字段。以transactions表为例其可用字段包括源码可见于 schema/index.tsid、account、category、amount整数单位分、payee、notes、date、imported_id、error、imported_payee、starting_balance_flag、transfer_id、sort_order、cleared、reconciled、tombstone、schedule、raw_synced_data。日期类字段使用YYYY-MM-DD字符串格式。四、处理拆分交易Split Transactions拆分交易会让聚合与选择变得复杂当对交易金额求和时是统计所有子交易还是只用顶层交易选择交易时你想要哪些记录transactions表为此提供了两种不同的数据接口通过options传入splits选项进行配置q(transactions).select(*).options({ splits: inline });inline默认值inline是默认行为不会返回拆分交易的 parent 交易只返回子交易结果是一个扁平数组。这样默认求和时就自然忽略了 parent 交易避免重复统计金额。groupedgrouped总是返回完整的拆分交易parent 全部子交易无论命中筛选条件的是哪一部分。返回的数据是分组的交易带有一个subtransactions属性列出其子交易。all与none文档脚注还提到第三种选项all以扁平列表同时返回交易与子交易仅在需要做高级处理时才用。而从源码 packages/loot-core/src/server/aql/schema/executors.ts 看合法的取值实际有四种function isValidSplitsOption(splits: string): splits is SplitsOption { return [all, inline, none, grouped].includes(splits); }其中none只返回 parent 交易不含子交易。若传入非法值执行器会抛出Invalid splits option for transactions错误见 executors.ts。源码级行为差异executors.ts 顶部注释给出了一个极具说明性的对比// q(transactions).select({ $count: id }) // q(transactions, { splits: grouped }).select({ $count: id }) // // The first will return the count of non-split and child // transactions, and the second will return the count of all parent // (or non-split) transactions即默认模式下计数包含普通交易与子交易grouped模式下计数只统计 parent或非拆分交易。对应的行为测试覆盖在 packages/loot-core/src/server/aql/schema/executors.test.ts 中包括splits: inline只返回非 parent 交易、splits: none只返回 parent、以及splits: grouped下的聚合查询等场景。此外subtransactions是一个特殊字段只有当表使用splits: grouped选项时才存在见 schema/index.ts 的注释。选择inline还是grouped本质上是选择面向金额汇总还是面向完整结构的数据视角——这四种选项给了你处理拆分交易的完全控制权。五、底层原理ActualQL 如何编译为 SQL理解 ActualQL 的工作机制有助于你写出更高效的查询。其核心管线位于 packages/loot-core/src/server/aql 目录schemaschema/index.ts定义各表字段、类型、引用关系以及表视图tableViews的构建逻辑——在 schema/index.ts 中可以看到视图构建时会根据tableOptions.splits决定如何拼接拆分交易数据默认splits为inlinecompilercompiler.ts将查询状态filter、select、group、order 表达式编译为 SQL 片段操作符在这里转换为对应的 SQL 运算符executorsschema/executors.ts负责执行编译结果其中execTransactions根据splits选项分发到execTransactionsBasic处理all/inline/none或execTransactionsGrouped处理grouped对结果按 parent 分组并附加subtransactionsexecexec.ts调用编译与执行入口compileAndRunAqlQuery/runCompiledAqlQuery并在 aql/index.ts 中对外暴露aqlQuery与aqlCompiledQuery。也就是说你写的q(transactions).filter({...}).select(...)会被编译成 SQL 执行$oneof变成IN、$like变成UNICODE_LIKE、$or变成OR分支等最终把 SQLite 的查询能力完整暴露给上层调用方。六、总结与延伸阅读ActualQL 把 Actual 应用内部的查询能力完整开放给了外部调用者通过q构建器与链式方法组合筛选、选择、排序、分组、聚合与分页通过 filter 操作符实现精确到字段的比较、正则与模糊匹配通过splits选项精确控制拆分交易的返回形态。无论你是在做账单导入脚本、财务报表还是数据迁移都可以复用 Actual 应用本身同款的能力。延伸阅读Transaction 字段参考拆分交易结构说明查看transactions表各字段的完整定义与拆分交易创建规则API 总览了解runQuery/aqlQuery之外的全部 API 方法API 查询构建器实现Query类各链式方法的源码核心查询引擎aqlQuery编译与执行入口拆分交易执行器测试splits各选项行为的具体测试用例。【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表