Beancount разполага с мощен, SQL-подобен език за заявки (BQL), който ви позволява да нарязвате, подреждате и анализирате финансовите си данни с точност. Независимо дали искате да генерирате бърз отчет, да отстраните грешка в запис или да извършите сложен анализ, овладяването на BQL е ключът към разгръщането на пълния потенциал на вашия счетоводен регистър в обикновен текст. Това ръководство ще ви преведе през неговата структура, функции и добри практики. 🔍

Структура и изпълнение на заявки
Ядрото на BQL е неговият познат, вдъхновен от SQL синтаксис. Изпълнявайте заявки с bea query: bea --file <ledger> query "SELECT …" отпечатва таблицата в терминала ви, а bea query без аргумент отваря интерактивната обвивка. Всяка заявка в това ръководство беше изпълнена срещу Beancount 3.2.3 с beanquery 0.2.0.
Основен формат на заявка
Една BQL заявка се състои от три основни клаузи: SELECT, FROM и WHERE.
SELECT <target1>, <target2>, ...
FROM <entry-filter-expression>
WHERE <posting-filter-expression>;SELECT: Указва кои колони от данни искате да извлечете.FROM: Филтрира цели транзакции преди да бъдат обработени.WHERE: Филтрира отделните редове на записите (postings) след като транзакцията е избрана.
Система за филтриране на две нива
Разбирането на разликата между клаузите FROM и WHERE е от решаващо значение за писането на точни заявки. BQL използва процес на филтриране на две нива.
-
Ниво на транзакция (
FROM) Тази клауза действа върху цели транзакции. Ако дадена транзакция отговаря на условиетоFROM, цялата транзакция (включително всички нейни записи) се предава на следващия етап. Това е основният начин за филтриране на данни, тъй като запазва целостта на системата за двойно счетоводство. Например филтриранетоFROM year = 2024избира всички транзакции, настъпили през 2024 г. -
Ниво на запис (
WHERE) Тази клауза филтрира отделните записи в рамките на транзакциите, избрани от клаузатаFROM. Това е полезно за представяне и за фокусиране върху конкретни страни на транзакция. Внимавайте обаче, че филтрирането на това ниво може да "разруши" целостта на транзакцията в изхода, тъй като може да видите само едната страна на запис. Например можете да изберете всички записи по вашата сметкаExpenses:Groceries.
Конкретно, PRINT FROM year = 2024 връща цели транзакции (двете страни на всеки запис), докато SELECT date, narration, account, position FROM year = 2024 WHERE account ~ "Assets:Broker" връща един ред за всеки съответстващ запис. В регистър с две покупки през брокер първата връща пълните записи, а втората връща точно двата реда за брокера.
Модел на данните
За да правите ефективни заявки към данните си, трябва да разберете как Beancount ги структурира. Един регистър е списък от директиви, но BQL се фокусира основно върху записите Transaction.
Структура на транзакция
Всяка Transaction е контейнер с атрибути от най-горно ниво и списък от обекти Posting.
Transaction
├── date
├── flag
├── payee
├── narration
├── tags
├── links
└── Postings[]
├── account
├── units
├── cost
├── price
└── metadataНалични типове колони
Можете да SELECT-нете всеки от атрибутите на транзакцията или на нейните записи.
-
Атрибути на транзакцията Тези колони са еднакви за всеки запис в рамките на една транзакция.
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]) -
Атрибути на записа Тези колони са специфични за всеки отделен ред на запис.
SELECT account, -- Името на сметката (str) position, -- Пълната сума, включително единици и себестойност (Position) units(position), -- Броят и валутата на записа (Amount) cost(position), -- Базата на себестойността на записа (Amount) price, -- Цената, използвана в записа (Amount) weight, -- Позицията, преобразувана в нейната база на себестойност (Amount) balance -- Текущият сбор на единиците в сметката (Inventory)unitsиcostса функции, които приематposition.price,weightиbalanceса обикновени колони.
Функции за заявки
BQL включва набор от функции за агрегиране и трансформиране на данни, подобно на SQL.
Функции за агрегиране
Функциите за агрегиране обобщават данни в множество редове. Когато се използват с GROUP BY, те предоставят групирани обобщения.
-- Преброяване на броя на записите
SELECT COUNT(*)
-- Сумиране на всички записи в един Inventory; валутите и партидите се запазват, не се конвертират
SELECT SUM(position)
-- един ред, напр. (-2300.00 USD, 10 HOOL {150.00 USD, 2024-09-05}, 5 HOOL {160.00 USD, 2024-11-02})
-- Сумиране на една сметка в една валута изрично (позициите без цена запазват валутата си)
SELECT SUM(CONVERT(position, 'USD')) WHERE account ~ "Assets:Checking"
-- един ред, напр. (2580.00 USD)
-- Намиране на датата на първата и последната транзакция
SELECT FIRST(date), LAST(date)
-- Намиране на минималната и максималната стойност на позицията
SELECT MIN(position), MAX(position)
-- Групиране по сметка, за да се получи сбор за всяка
SELECT account, SUM(position) GROUP BY accountФункции за позиция/инвентар
Колоната position е съставен обект. Тези функции ви позволяват да извлечете конкретни части от нея или да изчислите пазарната ѝ стойност.
-- Извличане само на числото и валутата от позиция
SELECT UNITS(position)
-- Показване на общата себестойност на позиция
SELECT COST(position)
-- Показване на всеки запис по неговата себестойност (колона; няма функция WEIGHT())
SELECT account, weight WHERE account ~ "Assets:Investments"
-- Изчисляване на пазарната стойност, използвайки най-новите ценови данни
-- (нужна е ценова директива за притежанието; в противен случай позицията се връща непроменена)
SELECT VALUE(position)Можете да комбинирате тези функции за мощни отчети. Например, за да видите общата себестойност и текущата пазарна стойност на инвестиционния си портфейл. Пазарната стойност се нуждае от директива price за всяко притежание (например 2024-12-01 price HOOL 175.00 USD). И двете агрегации връщат Inventory за всяка сметка, така че всяка валута все още е изброена отделно.
SELECT
account,
COST(SUM(position)) AS total_cost,
VALUE(SUM(position)) AS market_value
FROM
account ~ "Assets:Investments"
GROUP BY
account
-- един ред за сметка, напр. Assets:Broker:HOOL | (2300.00 USD) | (2625.00 USD)Ценовите директиви, използвани от заявките за пазарна стойност, могат да идват от ръчни записи, локален извлекател на котировки или Live Prices в съвместим зареждач. Управляваните потоци не променят синтаксиса на заявките. Локалните външни инструменти се нуждаят от локални ценови файлове, а историческите заявки все още се нуждаят от цени на или преди заявената дата.
Разширени функции
Отвъд основните 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 файлов формат. Той приема само филтър за записи. Клауза WHERE тук е синтактична грешка. За да стесните изхода до една страна на всеки запис, използвайте SELECT с филтър за записи вместо това. Той връща един ред за всеки съответстващ запис.
-- Отпечатване на всички транзакции от 2024 г. изцяло (всеки запис на всяко съответстващо вписване)
PRINT FROM year = 2024
-- Показване само на инвестиционните записи на транзакциите от 2024 г.
SELECT date, narration, account, position
FROM year = 2024
WHERE account ~ "Assets:Investments"
-- Намиране на транзакция по нейния уникален идентификатор (генериран от някои инструменти)
-- Връща съответстващото вписване или нула реда, когато нищо не носи този идентификатор
PRINT FROM id = "8e7c47250d040ae2b85de580dd4f5c2a"Филтриращи изрази
Можете да изграждате сложни филтри, използвайки логически оператори (AND, OR), регулярни изрази (~) и сравнения.
Низовите литерали използват единични кавички. Двойните кавички ограничават регулярния израз след ~.
-- Намиране на всички разходи за пътуване от втората половина на 2024 г.
-- SELECT * връща date, flag, payee, narration и position за всеки съответстващ запис
SELECT * FROM
year = 2024 AND month >= 6
WHERE account ~ "Expenses:Travel"
-- Намиране на всички транзакции, свързани с ваканция или бизнес
SELECT * FROM
'vacation-2024' IN tags OR
'business-trip' IN linksСъображения за производителност ⚙️
bea query е проектиран за ефективност, но разбирането на оперативния му поток може да ви помогне да пишете по-бързи заявки върху големи регистри.
- Зареждане на данни: Beancount първо анализира целия ви файл с регистъра и сортира всички транзакции хронологично. Целият този набор от данни се държи в паметта.
- Оптимизация на заявката: Машината за заявки прилага филтрите в определен ред за максимална ефективност:
FROM(транзакции) ->WHERE(записи) -> Агрегации. Филтрирането на нивоFROMе най-бързо, защото намалява набора от данни рано. - Използване на паметта: Всички операции се извършват в паметта. Обектите
Positionи агрегациитеInventoryса оптимизирани, но много големи набори от резултати могат да консумират значителна RAM памет. BQL не използва временно хранилище на диск.
Добри практики
Следвайте тези съвети, за да пишете чисти, ефективни и поддържаеми заявки.
-
Организация на заявките Форматирайте заявките си за четимост, особено сложните. Използвайте нови редове и отстъпи, за да разделите клаузите.
-- Чиста, четима заявка за всички разходи от 2024 г. SELECT date, account, position FROM year = 2024 WHERE account ~ "Expenses" ORDER BY date DESC; -
Отстраняване на грешки Ако дадена заявка не работи както се очаква, изпълнете първо малка извадка с
LIMIT. За да тествате филтър, използвайтеSELECT DISTINCT, за да видите кои уникални стойности съвпадат.-- Преглед на първите редове, докато итерирате SELECT date, account, position LIMIT 5; -- Тестване кои сметки съвпадат с регулярен израз SELECT DISTINCT account WHERE account ~ "^Assets:.*"; -
Проверки на баланса Можете да използвате BQL, за да проверите отново
balanceпроверките в регистъра си. Тази заявка трябва да върне точната сума, указана в последната ви проверка на баланса за тази сметка.-- Проверка на крайния баланс на разплащателната ви сметка SELECT account, sum(position) FROM close ON 2025-01-01 -- Използвайте датата от вашата директива за баланс WHERE account = "Assets:Checking";