Naar hoofdinhoud springen

Beancount Query Language (BQL): doorzoek je ledger

Doorzoek je Beancount-ledger met BQL, een SQL-achtige taal: SELECT-syntaxis, kolommen, aggregatie- en inventarisfuncties, en rapporten uitgevoerd met bea query.

Beancount beschikt over een krachtige, SQL-achtige Query Language (BQL) waarmee je je financiële gegevens met precisie kunt doorsnijden, verdelen en analyseren. Of je nu snel een rapport wilt genereren, een boeking wilt debuggen of complexe analyses wilt uitvoeren, het beheersen van BQL is de sleutel tot het ontsluiten van het volledige potentieel van je plaintext-boekhouding. Deze gids leidt je door de structuur, functies en best practices. 🔍

beancount.io BQL query-editor voert SQL-achtige query's uit tegen een Beancount-ledger in de browser

Verken het live ledger →


Querystructuur en uitvoering​

De kern van BQL is de vertrouwde, door SQL geïnspireerde syntaxis. Voer query's uit met bea query: bea --file <ledger> query "SELECT …" print de tabel in je terminal, en bea query zonder argument opent de interactieve shell. Elke query in deze gids is uitgevoerd tegen Beancount 3.2.3 met beanquery 0.2.0.

Basisqueryformaat​

Een BQL-query bestaat uit drie hoofdzinsdelen: SELECT, FROM en WHERE.

SELECT <target1>, <target2>, ...
FROM <entry-filter-expression>
WHERE <posting-filter-expression>;
  • SELECT: Geeft aan welke kolommen met gegevens je wilt ophalen.
  • FROM: Filtert hele transacties voordat ze worden verwerkt.
  • WHERE: Filtert de individuele boekingsregels nadat de transactie is geselecteerd.

Tweeledig filtersysteem​

Het begrijpen van het verschil tussen de FROM- en WHERE-clausules is cruciaal voor het schrijven van nauwkeurige query's. BQL gebruikt een tweetraps filterproces.

  1. Transactieniveau (FROM) Deze clausule werkt op hele transacties. Als een transactie voldoet aan de FROM-voorwaarde, wordt de hele transactie (inclusief al zijn boekingen) doorgegeven aan de volgende fase. Dit is de primaire manier om gegevens te filteren, omdat het de integriteit van het dubbel boekhouden behoudt. Het filteren op FROM year = 2024 selecteert bijvoorbeeld alle transacties die in 2024 hebben plaatsgevonden.

  2. Boekingsniveau (WHERE) Deze clausule filtert de individuele boekingen binnen de transacties die door de FROM-clausule zijn geselecteerd. Dit is handig voor presentatie en voor het focussen op specifieke onderdelen van een transactie. Houd er echter rekening mee dat filteren op dit niveau de integriteit van een transactie in de uitvoer kan "breken", omdat je mogelijk slechts één kant van een boeking ziet. Je zou bijvoorbeeld alle boekingen naar je Expenses:Groceries-rekening kunnen selecteren.

Concreet: PRINT FROM year = 2024 retourneert hele transacties (beide zijden van elke boeking), terwijl SELECT date, narration, account, position FROM year = 2024 WHERE account ~ "Assets:Broker" één rij per overeenkomende boeking retourneert. Op een ledger met twee broker-aankopen retourneert de eerste de volledige boekingen en de tweede precies de twee broker-rijen.


Datamodel​

Om je gegevens effectief te kunnen bevragen, moet je begrijpen hoe Beancount ze structureert. Een ledger is een lijst van directives, maar BQL richt zich voornamelijk op Transaction-boekingen.

Transactiestructuur​

Elke Transaction is een container met attributen op het hoogste niveau en een lijst met Posting-objecten.

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

Beschikbare kolomtypen​

Je kunt elk van de attributen van de transactie of zijn boekingen SELECT-en.

  1. Transactie-attributen Deze kolommen zijn hetzelfde voor elke boeking binnen één transactie.

    SELECT
        date,        -- De datum van de transactie (datetime.date)
        year,        -- Het jaar van de transactie (int)
        month,       -- De maand van de transactie (int)
        day,         -- De dag van de transactie (int)
        flag,        -- De transactievlag, bijv. "*" of "!" (str)
        payee,       -- De begunstigde (str)
        narration,   -- De beschrijving of memo (str)
        tags,        -- Een set tags, bijv. #trip-2024 (set[str])
        links        -- Een set links, bijv. ^expense-report (set[str])
  2. Boekingsattributen Deze kolommen zijn specifiek voor elke individuele boekingsregel.

    SELECT
        account,           -- De rekeningnaam (str)
        position,          -- Het volledige bedrag, inclusief units en cost (Position)
        units(position),   -- Het aantal en de valuta van de boeking (Amount)
        cost(position),    -- De kostprijsbasis van de boeking (Amount)
        price,             -- De prijs gebruikt in de boeking (Amount)
        weight,            -- De positie omgezet naar zijn kostprijsbasis (Amount)
        balance            -- Het lopende totaal van units in de rekening (Inventory)

    units en cost zijn functies die position als argument nemen. price, weight en balance zijn gewone kolommen.


Queryfuncties​

BQL bevat een suite aan functies voor aggregatie en datatransformatie, net als SQL.

Aggregatiefuncties​

Aggregatiefuncties vatten gegevens samen over meerdere rijen. In combinatie met GROUP BY leveren ze gegroepeerde samenvattingen.

-- Tel het aantal boekingen
SELECT COUNT(*)
 
-- Som alle boekingen op in één Inventory; valuta's en loten blijven behouden, worden niet omgerekend
SELECT SUM(position)
-- één rij, bijv. (-2300.00 USD, 10 HOOL {150.00 USD, 2024-09-05}, 5 HOOL {160.00 USD, 2024-11-02})
 
-- Totaliseer één rekening expliciet in één valuta (posities zonder prijs behouden hun valuta)
SELECT SUM(CONVERT(position, 'USD')) WHERE account ~ "Assets:Checking"
-- één rij, bijv. (2580.00 USD)
 
-- Vind de datum van de eerste en laatste transactie
SELECT FIRST(date), LAST(date)
 
-- Vind de minimum- en maximumpositiewaarden
SELECT MIN(position), MAX(position)
 
-- Groepeer op rekening om een som per rekening te krijgen
SELECT account, SUM(position) GROUP BY account

Positie-/inventarisfuncties​

De position-kolom is een samengesteld object. Met deze functies kun je specifieke onderdelen ervan extraheren of de marktwaarde berekenen.

-- Extraheer alleen het aantal en de valuta uit een positie
SELECT UNITS(position)
 
-- Toon de totale kostprijs van een positie
SELECT COST(position)
 
-- Toon elke boeking tegen zijn kostprijswaarde (een kolom; er is geen WEIGHT()-functie)
SELECT account, weight WHERE account ~ "Assets:Investments"
 
-- Bereken de marktwaarde met de meest recente prijsgegevens
-- (vereist een price-directive voor het bezit; anders wordt de positie ongewijzigd geretourneerd)
SELECT VALUE(position)

Je kunt deze combineren voor krachtige rapporten. Bijvoorbeeld om de totale kostprijs en huidige marktwaarde van je beleggingsportefeuille te zien. De marktwaarde vereist een price-directive voor elk bezit (bijvoorbeeld 2024-12-01 price HOOL 175.00 USD). Beide aggregaties retourneren een Inventory per rekening, dus elke valuta wordt nog steeds afzonderlijk vermeld.

SELECT
    account,
    COST(SUM(position)) AS total_cost,
    VALUE(SUM(position)) AS market_value
FROM
    account ~ "Assets:Investments"
GROUP BY
    account
-- één rij per rekening, bijv. Assets:Broker:HOOL | (2300.00 USD) | (2625.00 USD)

De price-directives die gebruikt worden door marktwaardequery's kunnen afkomstig zijn van handmatige invoer, een lokale quote-fetcher, of Live Prices in een compatibele loader. Beheerde feeds veranderen de querysyntaxis niet. Lokale upstream-tools hebben lokale prijsbestanden nodig, en historische query's hebben nog steeds prijzen nodig op of vóór de gevraagde datum.

Geavanceerde functies​

Naast basis-SELECT-statements biedt BQL gespecialiseerde commando's voor veelvoorkomende financiële rapporten.

Balansrapporten​

Het BALANCES-statement genereert een balans of resultatenrekening voor een specifieke periode.

-- Genereer een eenvoudige balans per het begin van 2024
BALANCES FROM close ON 2024-01-01
WHERE account ~ "^Assets|^Liabilities"
 
-- Genereer een resultatenrekening voor het fiscale jaar 2024
BALANCES FROM
    OPEN ON 2024-01-01
    CLOSE ON 2024-12-31
WHERE account ~ "^Income|^Expenses"

Journaalrapporten​

Het JOURNAL-statement toont de gedetailleerde activiteit voor één of meer rekeningen, vergelijkbaar met een traditionele grootboekweergave.

-- Toon alle activiteit in je betaalrekening tegen zijn oorspronkelijke kostprijs
JOURNAL "Assets:Checking" AT COST
 
-- Toon alle 401k-transacties, met alleen de units (aandelen)
JOURNAL "Assets:.*:401k" AT UNITS

Printbewerkingen​

Het PRINT-statement is een debugging-tool die volledige, overeenkomende transacties uitvoert in hun oorspronkelijke Beancount-bestandsformaat. Het accepteert alleen een entry-filter. Een WHERE-clausule is hier een syntaxisfout. Gebruik in plaats daarvan SELECT met een boekingsfilter om de uitvoer te beperken tot één zijde van elke boeking. Het retourneert één rij per overeenkomende boeking.

-- Print alle 2024-transacties volledig (elke boeking van elke overeenkomende boeking)
PRINT FROM year = 2024
 
-- Toon alleen de beleggingsboekingen van 2024-transacties
SELECT date, narration, account, position
FROM year = 2024
WHERE account ~ "Assets:Investments"
 
-- Vind een transactie op zijn unieke ID (gegenereerd door sommige tools)
-- Retourneert de overeenkomende boeking, of geen rijen wanneer niets die ID draagt
PRINT FROM id = "8e7c47250d040ae2b85de580dd4f5c2a"

Filterexpressies​

Je kunt geavanceerde filters bouwen met logische operatoren (AND, OR), reguliere expressies (~) en vergelijkingen.

String-literals gebruiken enkele aanhalingstekens. Dubbele aanhalingstekens begrenzen de reguliere expressie na ~.

-- Vind alle reiskosten uit de tweede helft van 2024
-- SELECT * retourneert date, flag, payee, narration en position per overeenkomende boeking
SELECT * FROM
    year = 2024 AND month >= 6
WHERE account ~ "Expenses:Travel"
 
-- Vind alle transacties gerelateerd aan een vakantie of zakenreis
SELECT * FROM
    'vacation-2024' IN tags OR
    'business-trip' IN links

Prestatieoverwegingen ⚙️​

bea query is ontworpen voor efficiëntie, maar inzicht in de operationele werking kan je helpen snellere query's te schrijven op grote ledgers.

  1. Gegevens laden: Beancount parseert eerst je volledige ledgerbestand en sorteert alle transacties chronologisch. Deze volledige dataset wordt in het geheugen gehouden.
  2. Query-optimalisatie: De query-engine past filters in een specifieke volgorde toe voor maximale efficiëntie: FROM (transacties) -> WHERE (boekingen) -> Aggregaties. Filteren op het FROM-niveau is het snelst omdat het de dataset vroegtijdig verkleint.
  3. Geheugengebruik: Alle bewerkingen vinden in het geheugen plaats. Position-objecten en Inventory-aggregaties zijn geoptimaliseerd, maar zeer grote resultaatsets kunnen aanzienlijk RAM verbruiken. BQL gebruikt geen tijdelijke opslag op schijf.

Best practices​

Volg deze tips om schone, effectieve en onderhoudbare query's te schrijven.

  1. Query-organisatie Formatteer je query's voor leesbaarheid, vooral complexe. Gebruik regelafbrekingen en inspringing om clausules te scheiden.

    -- Een schone, leesbare query voor alle kosten van 2024
    SELECT
        date,
        account,
        position
    FROM
        year = 2024
    WHERE
        account ~ "Expenses"
    ORDER BY
        date DESC;
  2. Debuggen Als een query niet werkt zoals verwacht, voer eerst een kleine steekproef uit met LIMIT. Gebruik SELECT DISTINCT om een filter te testen en te zien welke unieke waarden het matcht.

    -- Bekijk de eerste rijen terwijl je itereert
    SELECT date, account, position LIMIT 5;
     
    -- Test welke rekeningen overeenkomen met een reguliere expressie
    SELECT DISTINCT account
    WHERE account ~ "^Assets:.*";
  3. Balansasserties Je kunt BQL gebruiken om de balance-asserties in je ledger dubbel te controleren. Deze query zou exact het bedrag moeten retourneren dat in je laatste balanscontrole voor die rekening is opgegeven.

    -- Verifieer het eindsaldo van je betaalrekening
    SELECT account, sum(position)
    FROM close ON 2025-01-01 -- Gebruik de datum uit je balance-directive
    WHERE account = "Assets:Checking";

Bron: https://beancount.io/nl/docs/Basics/beancount-query-language