Zum Hauptinhalt springen

Beancount Query Language (BQL): Ledger abfragen

Führen Sie mit BQL, einer SQL-ähnlichen Sprache, Abfragen auf Ihrem Beancount-Journal durch: SELECT-Syntax, Spalten, Aggregat- und Bestandsfunktionen sowie Berichte, die mit bea query ausgeführt werden.

Beancount bietet eine leistungsstarke, SQL-ähnliche Query Language (BQL), mit der Sie Ihre Finanzdaten präzise aufschlüsseln und analysieren können. Ob Sie einen schnellen Bericht erstellen, einen Eintrag debuggen oder komplexe Analysen durchführen möchten – die Beherrschung von BQL ist der Schlüssel, um das volle Potenzial Ihres Plaintext-Buchhaltungsjournals auszuschöpfen. Diese Anleitung führt Sie durch die Struktur, die Funktionen und die bewährten Vorgehensweisen. 🔍

beancount.io BQL-Abfrageeditor führt SQL-ähnliche Abfragen gegen ein Beancount-Journal im Browser aus

Das Live-Journal erkunden →


Abfragestruktur und -ausführung​

Der Kern von BQL ist seine vertraute, von SQL inspirierte Syntax. Führen Sie Abfragen mit bea query aus: bea --file <ledger> query "SELECT …" gibt die Tabelle in Ihrem Terminal aus, und bea query ohne Argument öffnet die interaktive Shell. Jede Abfrage in dieser Anleitung wurde gegen Beancount 3.2.3 mit beanquery 0.2.0 ausgeführt.

Grundformat einer Abfrage​

Eine BQL-Abfrage besteht aus drei Hauptklauseln: SELECT, FROM und WHERE.

SELECT <target1>, <target2>, ...
FROM <entry-filter-expression>
WHERE <posting-filter-expression>;
  • SELECT: Gibt an, welche Datenspalten Sie abrufen möchten.
  • FROM: Filtert ganze Transaktionen, bevor sie verarbeitet werden.
  • WHERE: Filtert die einzelnen Buchungszeilen, nachdem die Transaktion ausgewählt wurde.

Zwei-Ebenen-Filtersystem​

Das Verständnis des Unterschieds zwischen den Klauseln FROM und WHERE ist entscheidend für das Schreiben korrekter Abfragen. BQL verwendet einen zweistufigen Filterprozess.

  1. Transaktionsebene (FROM) Diese Klausel wirkt auf ganze Transaktionen. Wenn eine Transaktion die FROM-Bedingung erfüllt, wird die gesamte Transaktion (einschließlich aller ihrer Buchungen) an die nächste Stufe weitergegeben. Dies ist die primäre Methode zum Filtern von Daten, da sie die Integrität des doppelten Buchführungssystems bewahrt. Beispielsweise wählt der Filter FROM year = 2024 alle Transaktionen aus, die im Jahr 2024 stattgefunden haben.

  2. Buchungsebene (WHERE) Diese Klausel filtert die einzelnen Buchungen innerhalb der durch die FROM-Klausel ausgewählten Transaktionen. Dies ist nützlich für die Darstellung und für die Fokussierung auf bestimmte Teile einer Transaktion. Beachten Sie jedoch, dass das Filtern auf dieser Ebene die Integrität einer Transaktion in der Ausgabe „brechen" kann, da Sie möglicherweise nur eine Seite eines Eintrags sehen. Beispielsweise könnten Sie alle Buchungen zu Ihrem Konto Expenses:Groceries auswählen.

Konkret gibt PRINT FROM year = 2024 ganze Transaktionen zurück (beide Seiten jedes Eintrags), während SELECT date, narration, account, position FROM year = 2024 WHERE account ~ "Assets:Broker" eine Zeile pro passender Buchung zurückgibt. Bei einem Journal mit zwei Broker-Käufen gibt die erste Abfrage die vollständigen Einträge zurück und die zweite genau die beiden Broker-Zeilen.


Datenmodell​

Um Ihre Daten effektiv abzufragen, müssen Sie verstehen, wie Beancount sie strukturiert. Ein Journal ist eine Liste von Direktiven, aber BQL konzentriert sich hauptsächlich auf Transaction-Einträge.

Transaktionsstruktur​

Jede Transaction ist ein Container mit Top-Level-Attributen und einer Liste von Posting-Objekten.

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

Verfügbare Spaltentypen​

Sie können jedes der Attribute aus der Transaktion oder ihren Buchungen SELECT-en.

  1. Transaktionsattribute Diese Spalten sind für jede Buchung innerhalb einer einzelnen Transaktion identisch.

    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. Buchungsattribute Diese Spalten sind spezifisch für jede einzelne Buchungszeile.

    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 und cost sind Funktionen, die position entgegennehmen. price, weight und balance sind einfache Spalten.


Abfragefunktionen​

BQL enthält eine Reihe von Funktionen für Aggregation und Datentransformation, ähnlich wie SQL.

Aggregationsfunktionen​

Aggregationsfunktionen fassen Daten über mehrere Zeilen zusammen. In Kombination mit GROUP BY liefern sie gruppierte Zusammenfassungen.

-- 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

Positions-/Bestandsfunktionen​

Die Spalte position ist ein zusammengesetztes Objekt. Mit diesen Funktionen können Sie bestimmte Teile daraus extrahieren oder ihren Marktwert berechnen.

-- 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)

Sie können diese für leistungsstarke Berichte kombinieren. Um beispielsweise die Gesamtkosten und den aktuellen Marktwert Ihres Anlageportfolios zu sehen. Der Marktwert benötigt eine price-Direktive für jede Position (zum Beispiel 2024-12-01 price HOOL 175.00 USD). Beide Aggregate geben ein Inventar pro Konto zurück, sodass jede Währung weiterhin separat aufgeführt wird.

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)

Die von Marktwert-Abfragen verwendeten Price-Direktiven können aus manuellen Einträgen, einem lokalen Kursabrufer oder Live Prices in einem kompatiblen Loader stammen. Verwaltete Feeds ändern die Abfragesyntax nicht. Lokale Upstream-Tools benötigen lokale Kursdateien, und historische Abfragen benötigen weiterhin Kurse zum oder vor dem angeforderten Datum.

Erweiterte Funktionen​

Über grundlegende SELECT-Anweisungen hinaus bietet BQL spezialisierte Befehle für gängige Finanzberichte.

Saldenberichte​

Die BALANCES-Anweisung erzeugt eine Bilanz oder Gewinn- und Verlustrechnung für einen bestimmten Zeitraum.

-- 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"

Journalberichte​

Die JOURNAL-Anweisung zeigt die detaillierte Aktivität für ein oder mehrere Konten, ähnlich einer traditionellen Journalansicht.

-- 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

Druckoperationen​

Die PRINT-Anweisung ist ein Debugging-Werkzeug, das vollständige, passende Transaktionen im ursprünglichen Beancount-Dateiformat ausgibt. Sie akzeptiert nur einen Eintragsfilter. Eine WHERE-Klausel ist hier ein Syntaxfehler. Um die Ausgabe auf eine Seite jedes Eintrags zu beschränken, verwenden Sie stattdessen SELECT mit einem Buchungsfilter. Es gibt eine Zeile pro passender Buchung zurück.

-- 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"

Filterausdrücke​

Sie können ausgefeilte Filter erstellen mit logischen Operatoren (AND, OR), regulären Ausdrücken (~) und Vergleichen.

String-Literale verwenden einfache Anführungszeichen. Doppelte Anführungszeichen begrenzen den regulären Ausdruck nach ~.

-- 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

Leistungsüberlegungen ⚙️​

bea query ist auf Effizienz ausgelegt, aber das Verständnis seines Betriebsablaufs kann Ihnen helfen, schnellere Abfragen auf großen Journalen zu schreiben.

  1. Datenladen: Beancount parst zunächst Ihre gesamte Journaldatei und sortiert alle Transaktionen chronologisch. Dieser gesamte Datensatz wird im Speicher gehalten.
  2. Abfrageoptimierung: Die Abfrage-Engine wendet Filter in einer bestimmten Reihenfolge für maximale Effizienz an: FROM (Transaktionen) -> WHERE (Buchungen) -> Aggregationen. Das Filtern auf FROM-Ebene ist am schnellsten, da es den Datensatz früh reduziert.
  3. Speichernutzung: Alle Operationen finden im Speicher statt. Position-Objekte und Inventory-Aggregationen sind optimiert, aber sehr große Ergebnismengen können erheblichen RAM verbrauchen. BQL verwendet keinen datenträgerbasierten temporären Speicher.

Bewährte Praktiken​

Befolgen Sie diese Tipps, um saubere, effektive und wartbare Abfragen zu schreiben.

  1. Abfrageorganisation Formatieren Sie Ihre Abfragen für die Lesbarkeit, insbesondere komplexe. Verwenden Sie Zeilenumbrüche und Einrückungen, um Klauseln zu trennen.

    -- A clean, readable query for all 2024 expenses
    SELECT
        date,
        account,
        position
    FROM
        year = 2024
    WHERE
        account ~ "Expenses"
    ORDER BY
        date DESC;
  2. Debugging Wenn eine Abfrage nicht wie erwartet funktioniert, führen Sie zuerst ein kleines Beispiel mit LIMIT aus. Um einen Filter zu testen, verwenden Sie SELECT DISTINCT, um zu sehen, welche eindeutigen Werte er trifft.

    -- 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. Saldenprüfungen Sie können BQL verwenden, um die balance-Prüfungen in Ihrem Journal zu verifizieren. Diese Abfrage sollte genau den Betrag zurückgeben, der in Ihrer letzten Saldenprüfung für dieses Konto angegeben wurde.

    -- 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";

Quelle: https://beancount.io/de/docs/Basics/beancount-query-language