Skip to content

Beancount Query Language (BQL) Specification

Overview

BQL is a specialized SQL-like query engine designed for financial data analysis. It operates on transaction postings while respecting double-entry bookkeeping constraints.

Query Structure

sql
SELECT <target1>, <target2>, ...
[FROM <entry-filter-expression>]
[WHERE <posting-filter-expression>]
[GROUP BY <columns>]
[ORDER BY <columns>]
[LIMIT <n>];

Two-Level Filtering

  • FROM clause: Filters entire transactions (preserving accounting equation)
  • WHERE clause: Filters postings from matching transactions

This distinction is critical: FROM preserves transaction integrity while WHERE selects specific postings.

Data Types

TypeDescriptionExample
StringText values"payee name"
DateISO date format2024-01-15
IntegerWhole numbers42
BooleanTruth valuesTRUE, FALSE
NumberDecimal precision123.45
Set of StringsCollectionstags, links
NULLAbsence of valueNULL
PositionSingle lot with optional costUnits + cost
InventoryAggregated positionsMultiple lots

Operators

Comparison Operators

OperatorMeaning
=Equals
!=Not equals
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal

Logical Operators

OperatorMeaning
ANDLogical and
ORLogical or
NOTLogical negation

Special Operators

OperatorMeaningExample
INSet membershipaccount IN ("Assets:Cash", "Assets:Bank")
~Regular expression matchaccount ~ "Expenses:"

NULL Handling

Unlike standard SQL, BQL uses binary NULL logic:

sql
NULL = NULL  -- yields TRUE (not NULL)

This simplifies queries when comparing optional values.

Column Types

Posting Columns (SELECT/WHERE)

ColumnTypeDescription
dateDateTransaction date
accountStringAccount name
positionPositionFull position with cost
unitsAmountUnits only
costAmountCost basis
weightAmountBalancing weight
narrationStringTransaction narration
payeeStringPayee
tagsSetTransaction tags
linksSetTransaction links
flagStringTransaction flag (* or !)
balanceInventoryRunning balance after posting

Entry Columns (FROM clause)

ColumnTypeDescription
dateDateDirective date
flagStringTransaction flag
payeeStringPayee
narrationStringNarration
tagsSetTags
linksSetLinks
idStringUnique stable hash
typeStringDirective type name

Functions

See functions.md for the complete function reference.

Position/Amount Functions

FunctionDescription
COST(pos)Total cost (units × per-unit cost)
UNITS(pos)Units only (strips cost)
NUMBER(amt)Numeric value from amount
CURRENCY(amt)Currency from amount
WEIGHT(pos)Balancing weight
VALUE(pos[, currency])Market value at last price

Date Functions

FunctionDescription
DAY(date)Day of month (1-31)
MONTH(date)Month (1-12)
YEAR(date)Year
QUARTER(date)Quarter (1-4)
WEEKDAY(date)Day of week (0=Monday)

String Functions

FunctionDescription
LENGTH(s)String length
UPPER(s)Uppercase
LOWER(s)Lowercase

Account Functions

FunctionDescription
PARENT(account)Parent account name
LEAF(account)Last component
ROOT(account, n)First n components

Aggregate Functions

FunctionDescription
COUNT(*)Count of postings
FIRST(x)First value in group
LAST(x)Last value in group
MIN(x)Minimum value
MAX(x)Maximum value
SUM(x)Sum (works on amounts, positions, inventories)

Query Types

Simple Query

One result row per matching posting:

sql
SELECT date, account, narration, position
WHERE account ~ "Expenses:";

Aggregate Query

One result row per group:

sql
SELECT account, SUM(position)
WHERE account ~ "Expenses:"
GROUP BY account;

Group keys MAY reference:

  • Column names
  • Ordinal indices (1, 2, ...)
  • Expressions

Result Control

DISTINCT

Remove duplicate result rows:

sql
SELECT DISTINCT account;

ORDER BY

Sort results:

sql
ORDER BY date DESC, account ASC;

Default is ASC. Multiple columns supported.

LIMIT

Restrict output:

sql
LIMIT 100;

Statement Operators

These transform transactions before posting projection.

OPEN ON <date>

Summarizes all entries before the date:

  • Asset/Liability balances → booked to Equity:Opening-Balances
  • Income/Expense balances → cleared to Equity:Earnings:Previous
sql
SELECT * FROM has_account("Invest") OPEN ON 2024-01-01;

CLOSE [ON <date>]

Truncates entries after the date:

sql
SELECT * FROM condition CLOSE ON 2024-12-31;

CLEAR

Transfers income and expense balances to equity:

sql
SELECT account, SUM(position)
FROM OPEN ON 2023-01-01 CLOSE ON 2024-01-01 CLEAR
WHERE account ~ "^(Assets|Liabilities)"
GROUP BY 1;

High-Level Query Shortcuts

JOURNAL

Generate account statement:

sql
JOURNAL <account-regexp> [AT <function>] [FROM ...]

Example:

sql
JOURNAL "Assets:Checking" AT cost

BALANCES

Produce account balance table:

sql
BALANCES [AT <function>] [FROM ...]

Example:

sql
BALANCES AT units FROM year = 2024

PRINT

Output filtered transactions in Beancount syntax:

sql
PRINT [FROM ...]

Wildcard Selection

sql
SELECT *;

Selects sensible default columns for the query type.

FROM Clause Filters

Special predicates for transaction-level filtering:

PredicateDescription
has_account(pattern)Transaction has posting matching account pattern
year = NTransaction year equals N
month = NTransaction month equals N
date >= DTransaction date comparison

Grammar Summary

query       := select_stmt | journal_stmt | balances_stmt | print_stmt

select_stmt := SELECT [DISTINCT] targets
               [FROM from_expr]
               [WHERE where_expr]
               [GROUP BY group_exprs]
               [ORDER BY order_exprs]
               [LIMIT n]

targets     := target ("," target)*
target      := expr [AS name]

from_expr   := [OPEN ON date] [CLOSE ON date] [CLEAR] [filter_expr]
filter_expr := predicate (AND predicate)*

where_expr  := condition (AND|OR condition)*
condition   := expr op expr | NOT condition | "(" where_expr ")"

group_exprs := expr ("," expr)*
order_exprs := expr [ASC|DESC] ("," expr [ASC|DESC])*

expr        := column | function(args) | literal | expr op expr

Key Distinctions from SQL

  1. Two-level filtering: FROM filters transactions, WHERE filters postings
  2. Native inventory types: Position and Inventory are first-class types
  3. Cost operations: Built-in functions for cost basis calculations
  4. Accounting equation preservation: Transaction-level filtering maintains balance
  5. Running balance column: balance without window functions
  6. Simplified NULL: Binary logic (NULL = NULL is TRUE)