Salta al contenuto principale

Beancount Query Language (BQL): interroga il tuo ledger

Interroga il tuo ledger Beancount con BQL, un linguaggio simile a SQL: sintassi SELECT, colonne, funzioni di aggregazione e inventario, e report eseguiti con bea query.

Beancount dispone di un potente Query Language (BQL) simile a SQL che ti permette di suddividere, analizzare e studiare i tuoi dati finanziari con precisione. Che tu voglia generare un report rapido, fare il debug di una scrittura o eseguire analisi complesse, padroneggiare BQL è la chiave per sbloccare il pieno potenziale del tuo registro contabile in plaintext. Questa guida ti accompagnerà attraverso la sua struttura, le funzioni e le best practice. 🔍

beancount.io BQL query editor running SQL-like queries against a Beancount ledger in the browser

Explore the live ledger →


Struttura ed Esecuzione delle Query​

Il cuore di BQL è la sua familiare sintassi ispirata a SQL. Esegui le query con bea query: bea --file <ledger> query "SELECT …" stampa la tabella nel tuo terminale, e bea query senza argomenti apre la shell interattiva. Ogni query in questa guida è stata eseguita con Beancount 3.2.3 con beanquery 0.2.0.

Struttura Base di una Query​

Una query BQL è composta da tre clausole principali: SELECT, FROM e WHERE.

SELECT <target1>, <target2>, ...
FROM <entry-filter-expression>
WHERE <posting-filter-expression>;
  • SELECT: Specifica quali colonne di dati vuoi recuperare.
  • FROM: Filtra intere transazioni prima che vengano elaborate.
  • WHERE: Filtra le singole righe di posting dopo che la transazione è stata selezionata.

Sistema di Filtraggio su Due Livelli​

Comprendere la differenza tra le clausole FROM e WHERE è cruciale per scrivere query accurate. BQL utilizza un processo di filtraggio a due livelli.

  1. Livello transazione (FROM) Questa clausola agisce su intere transazioni. Se una transazione corrisponde alla condizione FROM, l'intera transazione (incluse tutte le sue scritture) viene passata alla fase successiva. Questo è il modo principale per filtrare i dati, poiché preserva l'integrità del sistema di partita doppia. Ad esempio, filtrando FROM year = 2024 si selezionano tutte le transazioni avvenute nel 2024.

  2. Livello posting (WHERE) Questa clausola filtra le singole scritture all'interno delle transazioni selezionate dalla clausola FROM. Questo è utile per la presentazione e per concentrarsi su specifiche parti di una transazione. Tuttavia, tieni presente che filtrare a questo livello può "rompere" l'integrità di una transazione nell'output, poiché potresti vedere solo un lato di una scrittura. Ad esempio, potresti selezionare tutte le scritture sul tuo conto Expenses:Groceries.

Concretamente, PRINT FROM year = 2024 restituisce le transazioni complete (entrambi i lati di ogni scrittura), mentre SELECT date, narration, account, position FROM year = 2024 WHERE account ~ "Assets:Broker" restituisce una riga per ogni posting corrispondente. Su un registro con due acquisti tramite broker, la prima restituisce le scritture complete e la seconda restituisce esattamente le due righe del broker.


Modello dei Dati​

Per interrogare i tuoi dati in modo efficace, devi capire come Beancount li struttura. Un registro è una lista di direttive, ma BQL si concentra principalmente sulle scritture Transaction.

Struttura della Transazione​

Ogni Transaction è un contenitore con attributi di primo livello e una lista di oggetti Posting.

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

Tipi di Colonne Disponibili​

Puoi usare SELECT su qualsiasi attributo della transazione o delle sue scritture.

  1. Attributi della transazione Queste colonne sono identiche per ogni posting all'interno di una singola transazione.

    SELECT
        date,        -- The date of the transaction (datetime.date)
        year,        -- The year of the transaction (int)
        month,       -- The month of the transaction (int)
        day,         -- The day of the transaction (int)
        flag,        -- The transaction flag, e.g., "*" or "!" (str)
        payee,       -- The payee (str)
        narration,   -- The description or memo (str)
        tags,        -- A set of tags, e.g., #trip-2024 (set[str])
        links        -- A set of links, e.g., ^expense-report (set[str])
  2. Attributi del posting Queste colonne sono specifiche di ogni singola riga di scrittura.

    SELECT
        account,           -- The account name (str)
        position,          -- The full amount, including units and cost (Position)
        units(position),   -- The number and currency of the posting (Amount)
        cost(position),    -- The cost basis of the posting (Amount)
        price,             -- The price used in the posting (Amount)
        weight,            -- The position converted to its cost basis (Amount)
        balance            -- The running total of units in the account (Inventory)

    units e cost sono funzioni che accettano position. price, weight e balance sono colonne semplici.


Funzioni di Query​

BQL include una suite di funzioni per l'aggregazione e la trasformazione dei dati, proprio come SQL.

Funzioni di Aggregazione​

Le funzioni di aggregazione riassumono i dati su più righe. Se utilizzate con GROUP BY, forniscono riepiloghi raggruppati.

-- Count the number of postings
SELECT COUNT(*)
 
-- Sum all postings into one Inventory; currencies and lots are kept, not converted
SELECT SUM(position)
-- one row, e.g. (-2300.00 USD, 10 HOOL {150.00 USD, 2024-09-05}, 5 HOOL {160.00 USD, 2024-11-02})
 
-- Total one account in a single currency explicitly (positions without a price keep their currency)
SELECT SUM(CONVERT(position, 'USD')) WHERE account ~ "Assets:Checking"
-- one row, e.g. (2580.00 USD)
 
-- Find the date of the first and last transaction
SELECT FIRST(date), LAST(date)
 
-- Find the minimum and maximum position values
SELECT MIN(position), MAX(position)
 
-- Group by account to get a sum for each
SELECT account, SUM(position) GROUP BY account

Funzioni di Posizione/Inventario​

La colonna position è un oggetto composito. Queste funzioni ti permettono di estrarne parti specifiche o di calcolarne il valore di mercato.

-- Extract just the number and currency from a position
SELECT UNITS(position)
 
-- Show the total cost of a position
SELECT COST(position)
 
-- Show each posting at its cost value (a column; there is no WEIGHT() function)
SELECT account, weight WHERE account ~ "Assets:Investments"
 
-- Calculate the market value using the latest price data
-- (needs a price directive for the holding; otherwise the position is returned unchanged)
SELECT VALUE(position)

Puoi combinarle per creare report potenti. Ad esempio, per vedere il costo totale e il valore di mercato corrente del tuo portafoglio di investimenti. Il valore di mercato necessita di una direttiva price per ogni holding (ad esempio 2024-12-01 price HOOL 175.00 USD). Entrambe le aggregazioni restituiscono un Inventory per conto, quindi ogni valuta è ancora elencata separatamente.

SELECT
    account,
    COST(SUM(position)) AS total_cost,
    VALUE(SUM(position)) AS market_value
FROM
    account ~ "Assets:Investments"
GROUP BY
    account
-- one row per account, e.g. Assets:Broker:HOOL | (2300.00 USD) | (2625.00 USD)

Le direttive price utilizzate dalle query sul valore di mercato possono provenire da voci manuali, da un fetcher locale di quotazioni o da Live Prices in un loader compatibile. I feed gestiti non cambiano la sintassi delle query. Gli strumenti upstream locali necessitano di file di prezzi locali, e le query storiche richiedono comunque prezzi alla data richiesta o precedenti.

Funzionalità Avanzate​

Oltre alle istruzioni SELECT di base, BQL offre comandi specializzati per i report finanziari più comuni.

Report di Bilancio​

L'istruzione BALANCES genera uno stato patrimoniale o un conto economico per un periodo specifico.

-- Generate a simple balance sheet as of the start of 2024
BALANCES FROM close ON 2024-01-01
WHERE account ~ "^Assets|^Liabilities"
 
-- Generate an income statement for the 2024 fiscal year
BALANCES FROM
    OPEN ON 2024-01-01
    CLOSE ON 2024-12-31
WHERE account ~ "^Income|^Expenses"

Report di Giornale​

L'istruzione JOURNAL mostra l'attività dettagliata di uno o più conti, in modo simile a una tradizionale vista di registro.

-- Show all activity in your checking account at its original cost
JOURNAL "Assets:Checking" AT COST
 
-- Show all 401k transactions, displaying only the units (shares)
JOURNAL "Assets:.*:401k" AT UNITS

Operazioni di Stampa​

L'istruzione PRINT è uno strumento di debug che produce transazioni complete e corrispondenti nel loro formato originale di file Beancount. Accetta solo un filtro sulle scritture. Una clausola WHERE qui è un errore di sintassi. Per restringere l'output a un lato di ogni scrittura, usa SELECT con un filtro sui posting. Restituisce una riga per ogni posting corrispondente.

-- Print all 2024 transactions in full (every posting of each matching entry)
PRINT FROM year = 2024
 
-- Show only the investment postings of 2024 transactions
SELECT date, narration, account, position
FROM year = 2024
WHERE account ~ "Assets:Investments"
 
-- Find a transaction by its unique ID (generated by some tools)
-- Returns the matching entry, or no rows when nothing carries that ID
PRINT FROM id = "8e7c47250d040ae2b85de580dd4f5c2a"

Espressioni di Filtraggio​

Puoi costruire filtri sofisticati usando operatori logici (AND, OR), espressioni regolari (~) e confronti.

I letterali stringa usano virgolette singole. Le virgolette doppie delimitano l'espressione regolare dopo ~.

-- Find all travel expenses from the second half of 2024
-- SELECT * returns date, flag, payee, narration and position per matching posting
SELECT * FROM
    year = 2024 AND month >= 6
WHERE account ~ "Expenses:Travel"
 
-- Find all transactions related to a vacation or business
SELECT * FROM
    'vacation-2024' IN tags OR
    'business-trip' IN links

Considerazioni sulle Prestazioni​

bea query è progettato per l'efficienza, ma comprendere il suo flusso operativo può aiutarti a scrivere query più veloci su registri di grandi dimensioni.

  1. Caricamento dei dati: Beancount prima analizza l'intero file del tuo registro e ordina tutte le transazioni cronologicamente. L'intero dataset è mantenuto in memoria.
  2. Ottimizzazione della query: Il motore di query applica i filtri in un ordine specifico per la massima efficienza: FROM (transazioni) -> WHERE (postings) -> Aggregazioni. Filtrare a livello FROM è più veloce perché riduce il dataset in anticipo.
  3. Utilizzo della memoria: Tutte le operazioni avvengono in memoria. Gli oggetti Position e le aggregazioni Inventory sono ottimizzati, ma set di risultati molto grandi possono consumare RAM significativa. BQL non utilizza archiviazione temporanea su disco.

Migliori Pratiche​

Segui questi consigli per scrivere query pulite, efficaci e manutenibili.

  1. Organizzazione delle query Formatta le tue query per la leggibilità, specialmente quelle complesse. Usa interruzioni di riga e indentazione per separare le clausole.

    -- A clean, readable query for all 2024 expenses
    SELECT
        date,
        account,
        position
    FROM
        year = 2024
    WHERE
        account ~ "Expenses"
    ORDER BY
        date DESC;
  2. Debugging Se una query non funziona come previsto, esegui prima un piccolo campione con LIMIT. Per testare un filtro, usa SELECT DISTINCT per vedere quali valori unici corrisponde.

    -- Preview the first rows while iterating
    SELECT date, account, position LIMIT 5;
     
    -- Test which accounts match a regular expression
    SELECT DISTINCT account
    WHERE account ~ "^Assets:.*";
  3. Assertion di saldo Puoi usare BQL per ricontrollare le assertion di balance nel tuo registro. Questa query dovrebbe restituire l'importo esatto specificato nel tuo ultimo controllo di saldo per quel conto.

    -- Verify the final balance of your checking account
    SELECT account, sum(position)
    FROM close ON 2025-01-01 -- Use the date from your balance directive
    WHERE account = "Assets:Checking";

Fonte: https://beancount.io/it/docs/Basics/beancount-query-language