跳到主要内容

Beancount 查询语言 - 类 SQL 财务查询

Beancount 具有强大的类 SQL 查询语言 (BQL),可让您精确地切片、切块和分析您的财务数据。 无论您是想生成快速报告、调试条目还是执行复杂分析,掌握 BQL 都是释放您的纯文本会计账本的全部潜力的关键。 本指南将引导您了解其结构、功能和最佳实践。 🔍


查询结构和执行

BQL 的核心是其熟悉的、受 SQL 启发的语法。 查询使用 bean-query 命令行工具执行,该工具处理您的账本文件并将结果直接返回到您的终端。

基本查询格式

BQL 查询由三个主要子句组成:SELECTFROMWHERE

SELECT <target1>, <target2>, ...
FROM <entry-filter-expression>
WHERE <posting-filter-expression>;
  • SELECT: 指定要检索的数据列。
  • FROM: 在处理整个交易_之前_对其进行过滤。
  • WHERE: 在选择交易后,过滤单个过账行。

两级过滤系统

理解 FROMWHERE 子句之间的区别对于编写准确的查询至关重要。 BQL 使用两级过滤过程。

  1. 交易级别 (FROM) 此子句对整个交易执行操作。 如果交易符合 FROM 条件,则_整个交易_(包括其所有过账)将传递到下一阶段。 这是过滤数据的主要方式,因为它保留了复式记账系统的完整性。 例如,过滤 FROM year = 2024 选择 2024 年发生的所有交易。

  2. 过账级别 (WHERE) 此子句过滤由 FROM 子句选择的交易_内_的各个过账。 这对于演示和关注交易的特定部分很有用。 但是,请注意,在此级别进行过滤可能会“破坏”输出中交易的完整性,因为您可能只会看到条目的一侧。 例如,您可以选择所有到您的 Expenses:Groceries 帐户的过账。


数据模型

要有效地查询您的数据,您需要了解 Beancount 如何构建它。 账本是一个指令列表,但 BQL 主要关注 Transaction 条目。

交易结构

每个 Transaction 都是一个容器,具有顶级属性和 Posting 对象列表。

Transaction
├── date
├── flag
├── payee
├── narration
├── tags
├── links
└── Postings[]
├── account
├── units
├── cost
├── price
└── metadata

可用列类型

您可以 SELECT 交易或其过账中的任何属性。

  1. 交易属性 对于单个交易中的每个过账,这些列都是相同的。

    SELECT
    date, -- 交易的日期 (datetime.date)
    year, -- 交易的年份 (int)
    month, -- 交易的月份 (int)
    day, -- 交易的日期 (int)
    flag, -- 交易标志,例如,“*”或“!” (str)
    payee, -- 收款人 (str)
    narration, -- 描述或备注 (str)
    tags, -- 一组标签,例如,#trip-2024 (set[str])
    links -- 一组链接,例如,^expense-report (set[str])
  2. 过账属性 这些列特定于每个单独的过账行。

    SELECT
    account, -- 帐户名称 (str)
    position, -- 完整金额,包括单位和成本 (Position)
    units, -- 过账的数量和货币 (Amount)
    cost, -- 过账的成本基础 (Cost)
    price, -- 过账中使用的价格 (Amount)
    weight, -- 转换为其成本基础的头寸 (Amount)
    balance -- 帐户中单位的运行总计 (Inventory)

查询函数

BQL 包括一套用于聚合和数据转换的函数,很像 SQL。

聚合函数

聚合函数汇总多行数据。 与 GROUP BY 一起使用时,它们提供分组汇总。

-- 统计过账的数量
SELECT COUNT(*)

-- 对所有过账的值求和(转换为通用货币)
SELECT SUM(position)

-- 查找第一个和最后一个交易的日期
SELECT FIRST(date), LAST(date)

-- 查找最小和最大头寸值
SELECT MIN(position), MAX(position)

-- 按帐户分组以获取每个帐户的总和
SELECT account, SUM(position) GROUP BY account

头寸/库存函数

position 列是一个复合对象。 这些函数允许您提取它的特定部分或计算其市场价值。

-- 仅从头寸中提取数量和货币
SELECT UNITS(position)

-- 显示头寸的总成本
SELECT COST(position)

-- 显示转换为其成本基础的头寸(对投资有用)
SELECT WEIGHT(position)

-- 使用最新价格数据计算市场价值
SELECT VALUE(position)

您可以将这些组合起来以生成强大的报告。 例如,要查看您的投资组合的总成本和当前市场价值:

SELECT
account,
COST(SUM(position)) AS total_cost,
VALUE(SUM(position)) AS market_value
FROM
account ~ "Assets:Investments"
GROUP BY
account

高级功能

除了基本的 SELECT 语句之外,BQL 还为常见的财务报告提供专用命令。

余额报告

BALANCES 语句生成特定期间的资产负债表或损益表。

-- 生成截至 2024 年初的简单资产负债表
BALANCES FROM close ON 2024-01-01
WHERE account ~ "^Assets|^Liabilities"

-- 生成 2024 财年的损益表
BALANCES FROM
OPEN ON 2024-01-01
CLOSE ON 2024-12-31
WHERE account ~ "^Income|^Expenses"

日记账报告

JOURNAL 语句显示一个或多个帐户的详细活动,类似于传统的总账视图。

-- 以原始成本显示您的支票帐户中的所有活动
JOURNAL "Assets:Checking" AT COST

-- 显示所有 401k 交易,仅显示单位(份额)
JOURNAL "Assets:.*:401k" AT UNITS

打印操作

PRINT 语句是一个调试工具,以其原始 Beancount 文件格式输出完整的匹配交易。

-- 打印 2024 年所有与投资相关的交易
PRINT FROM year = 2024
WHERE account ~ "Assets:Investments"

-- 通过其唯一 ID 查找交易(由某些工具生成)
PRINT FROM id = "8e7c47250d040ae2b85de580dd4f5c2a"

过滤表达式

您可以使用逻辑运算符 (ANDOR)、正则表达式 (~) 和比较来构建复杂的过滤器。

-- 查找 2024 年下半年的所有差旅费
SELECT * FROM
year = 2024 AND month >= 6
WHERE account ~ "Expenses:Travel"

-- 查找与度假或商务相关的所有交易
SELECT * FROM
"vacation-2024" IN tags OR
"business-trip" IN links

性能注意事项 ⚙️

bean-query 旨在提高效率,但了解其操作流程可以帮助您在大型账本上编写更快的查询。

  1. 数据加载: Beancount 首先解析您的整个账本文件并按时间顺序对所有交易进行排序。 整个数据集都保存在内存中。
  2. 查询优化: 查询引擎以特定顺序应用过滤器以实现最大效率:FROM(交易)-> WHERE(过账)-> 聚合。 在 FROM 级别进行过滤是最快的,因为它会提前减少数据集。
  3. 内存使用: 所有操作都在内存中进行。 Position 对象和 Inventory 聚合已优化,但非常大的结果集可能会消耗大量 RAM。 BQL 不使用基于磁盘的临时存储。

最佳实践

请遵循以下提示来编写清晰、有效且可维护的查询。

  1. 查询组织 格式化您的查询以提高可读性,尤其是复杂的查询。 使用换行符和缩进来分隔子句。

    -- 一个清晰、可读的查询,用于所有 2024 年的费用
    SELECT
    date,
    account,
    position
    FROM
    year = 2024
    WHERE
    account ~ "Expenses"
    ORDER BY
    date DESC;
  2. 调试 如果查询未按预期工作,请使用 EXPLAIN 查看 Beancount 如何解析它。 要测试过滤器,请使用 SELECT DISTINCT 查看它匹配的唯一值。

    -- 查看查询计划
    EXPLAIN SELECT date, account, position;

    -- 测试哪些帐户与正则表达式匹配
    SELECT DISTINCT account
    WHERE account ~ "^Assets:.*";
  3. 余额断言 您可以使用 BQL 来仔细检查您的账本中的 balance 断言。 此查询应返回您上次对该帐户进行余额检查时指定的准确金额。

    -- 验证您的支票帐户的最终余额
    SELECT account, sum(position)
    FROM close ON 2025-01-01 -- 使用您的余额指令中的日期
    WHERE account = "Assets:Checking";