Built-in Queries
Zhang’s read endpoints use built-in queries (#479). A built-in query is a named query in Zhang’s query language that a page runs against your ledger, with a few parameters such as the dates of a report. The page only arranges the result, so the logic behind a figure is a query you can read on this page.
You can open the query behind a figure on the Query page, with the values the page used filled in, and change it: another date range, more accounts, another grouping, a chart. Anything the app shows, you can also query, and vary, without waiting for a new release.
Opening a query
Section titled “Opening a query”A figure that comes from a built-in query has an Open query action. It opens the Query page (/explore) with the query in the editor and runs it. The parameters are written into the query as values, so the query in the editor is complete. Edit it and run it again like any other query, or save it in your ledger with a query directive.
Parameters
Section titled “Parameters”A built-in query names its inputs as parameters, written :name, such as WHERE date >= :from AND date <= :to. Zhang binds their values when it runs the query. What you type into a page, such as a search keyword, is only ever bound as a parameter and never pasted into the query text.
When a query is opened on the Query page, every parameter is replaced by its value, written so that the query reads it back exactly:
| Type | Written as | Examples |
|---|---|---|
date |
YYYY-MM-DD |
2024-01-31 |
str |
in single quotes, or in double quotes if it contains a single quote | 'Assets:Bank', "O'Brien" |
set |
a call of set |
set('food', 'trip'), set() |
int |
the number, in parentheses if negative | 12, (-3) |
decimal |
the number with its decimal places, always with a point, in parentheses if negative | 12.50, 12., (-0.5) |
bool |
TRUE or FALSE |
|
| any type | NULL for no value, which usually leaves a filter out |
NULL |
Strings in the query language have no escape sequences: a string runs to the next quote of the same kind, and backslashes stay as they are, so 'C:\temp\' is the text C:\temp\. A text that contains both kinds of quotes is written as a concatenation, such as ("it's " + '"quoted"').
The query with its values written in returns the same rows as the page got. Only the name of a column written without AS changes, since it is the column’s text.
HTTP API
Section titled “HTTP API”Both endpoints sit behind the same authentication as the rest of the API.
List the built-in queries
Section titled “List the built-in queries”GET /api/query/builtins lists every built-in query with its name, a description, its BQL and its parameters with their types:
{ "data": [ { "name": "postings.between", "description": "Every posting between two ledger dates, both included, in ledger order.", "bql": "SELECT date, flag, payee, narration, account, position WHERE date >= :from AND date <= :to ORDER BY seq", "params": [ { "name": "from", "type": "date" }, { "name": "to", "type": "date" } ] } ]}Write a query out with its values
Section titled “Write a query out with its values”POST /api/query/builtins/{name}/text returns the query name with its parameters replaced by the values in params, as the Query page receives it:
curl -X POST http://localhost:8000/api/query/builtins/postings.between/text \ -H 'Content-Type: application/json' \ -d '{"params": {"from": "2024-01-01", "to": "2024-01-31"}}'{ "data": { "query": "SELECT date, flag, payee, narration, account, position WHERE date >= 2024-01-01 AND date <= 2024-01-31 ORDER BY seq" }}Send the text to POST /api/query to run it. params must give every parameter of the query, and no other, as a JSON value of its type:
| Type | JSON value |
|---|---|
date |
a string YYYY-MM-DD |
str |
a string |
set |
a list of strings |
int |
an integer |
decimal |
a number, or a string such as "12.50" to keep the decimal places |
bool |
true or false |
| any type | null |
An unknown query name is answered with HTTP 404. A missing, unknown or mistyped parameter is answered with HTTP 400 and a message that names it.
The queries
Section titled “The queries”General
Section titled “General”postings.between
Section titled “postings.between”Every posting between two ledger dates, both included, in ledger order.
| Parameter | Type | Value |
|---|---|---|
from |
date |
the first day |
to |
date |
the last day |
SELECT date, flag, payee, narration, account, positionWHERE date >= :from AND date <= :toORDER BY seqpostings.matching
Section titled “postings.matching”The postings of the transactions with a payee and any of some tags, in ledger order; a NULL parameter leaves its filter out.
| Parameter | Type | Value |
|---|---|---|
payee |
str |
the payee, or NULL for any |
tags |
set |
the tags, any of which a transaction must have, or NULL for any |
SELECT date, payee, narration, tags, account, positionWHERE (:payee IS NULL OR payee = :payee) AND (:tags IS NULL OR intersects(tags, :tags))ORDER BY seqReport
Section titled “Report”The Report page and the dashboard (GET /api/statistic/summary, /api/statistic/graph and /api/statistic/{account_type}). Their range is two ledger dates, from and to, both included; the endpoints also accept an instant, which stands for its day in the ledger’s timezone. currency is the ledger’s operating currency.
- Valuation. The summary and the rankings are valued at the prices of
to. Each point of the graph is valued at the prices of its own last day, or oftofor the last one. A price is used in either direction, and a holding at cost without a price of its own is valued through its cost currency (seeconvert). Amounts that no price converts keep their currency and are left out of the totals in the operating currency. - The graph has one point per day, per week (Monday to Sunday) or per month, named by its first day, so its first and last weeks or months may reach outside the range; the Report page labels the first one by the first day of the range. A point without postings in the range has the net worth of the point before, or the net worth on the day before
from, valued at its own last day (seereport.net_worth_trend). - Limits. A graph has at most 50,000 points, about 137 years of days; a longer range by day is answered with HTTP 400, so ask for weeks or months. The figures also obey the limits of every query: the graph’s points, with a value per currency, count against the result size limit (
ZHANG_QUERY_MAX_RESULT_VALUES), and a graph that goes over it, or over the time limit, is answered with HTTP 400 too. What a graph costs grows with its range, not with the history of the ledger before it.
report.net_worth
Section titled “report.net_worth”The net worth, the balance of the assets and the liabilities, at the end of to, valued in currency at the prices of that day: the summary’s balance. balance keeps the lots.
| Parameter | Type | Value |
|---|---|---|
to |
date |
the day of the balance |
currency |
str |
the currency of the values |
SELECT sum(position) AS balance, units(sum(position)) AS units, convert(sum(position), :currency, :to) AS valueWHERE (under(account, 'Assets') OR under(account, 'Liabilities')) AND date <= :toreport.liabilities
Section titled “report.liabilities”The balance of the liabilities at the end of to, valued in currency at the prices of that day. It is negative, as in the ledger.
| Parameter | Type | Value |
|---|---|---|
to |
date |
the day of the balance |
currency |
str |
the currency of the values |
SELECT units(sum(position)) AS units, convert(sum(position), :currency, :to) AS valueWHERE under(account, 'Liabilities') AND date <= :toreport.flows
Section titled “report.flows”The income and the expenses of the range, valued in currency at the prices of to. Income is negative, as in the ledger.
| Parameter | Type | Value |
|---|---|---|
from |
date |
the first day |
to |
date |
the last day |
currency |
str |
the currency of the values |
SELECT root(account, 1) AS type, units(sum(position)) AS units, convert(sum(position), :currency, :to) AS valueWHERE (under(account, 'Income') OR under(account, 'Expenses')) AND date >= :from AND date <= :toGROUP BY typeORDER BY typereport.transaction_count
Section titled “report.transaction_count”The number of transactions of the range. The padding transactions of balance ... with pad (flag P) are not counted, and balance assertions are not transactions.
| Parameter | Type | Value |
|---|---|---|
from |
date |
the first day |
to |
date |
the last day |
SELECT count(*) AS transactionsFROM #transactionsWHERE flag != 'P' AND date >= :from AND date <= :toreport.net_worth_trend
Section titled “report.net_worth_trend”The net worth, the balance of the assets and the liabilities, at the end of every day, week or month of the range that has postings, valued in currency at the prices of its last day in the range. OPEN ON :from replaces everything before from with opening balances dated the day before, lot by lot, so the running balance starts from them and the query only groups the days of the range, plus the bucket of that day before. interval is '1 day', '1 week' or '1 month': the bins of date_bin from 2001-01-01, a Monday and the first of a month, are calendar days, weeks starting on Monday and months, each named by its first day. least keeps the last bin’s valuation date within the range. balance keeps the lots, to value the points without postings.
The query lists only the days, weeks or months with postings, so Open query shows fewer rows than the chart has points. The chart fills a day, week or month without postings with the last balance before it, the opening balance included, valued at its own last day in the range, as report.net_worth of that day values it. The query language cannot list days without postings yet.
| Parameter | Type | Value |
|---|---|---|
from |
date |
the first day |
to |
date |
the last day |
interval |
str |
'1 day', '1 week' or '1 month' |
currency |
str |
the currency of the values |
SELECT date_bin(:interval, date, 2001-01-01) AS bucket, last(balance) AS balance, units(last(balance)) AS units, convert(last(balance), :currency, least(max(date_bin(:interval, date, 2001-01-01)) + interval(:interval) - 1, :to)) AS valueFROM OPEN ON :fromWHERE (under(account, 'Assets') OR under(account, 'Liabilities')) AND date <= :toGROUP BY bucketORDER BY bucketreport.changes
Section titled “report.changes”What each account type changed by in every day, week or month of the range, valued in currency at the prices of its last day in the range: the bars of the income and expenses chart. The bins are those of report.net_worth_trend; the first one only counts the postings from from on.
| Parameter | Type | Value |
|---|---|---|
from |
date |
the first day |
to |
date |
the last day |
interval |
str |
'1 day', '1 week' or '1 month' |
currency |
str |
the currency of the values |
SELECT date_bin(:interval, date, 2001-01-01) AS bucket, root(account, 1) AS type, units(sum(position)) AS units, convert(sum(position), :currency, least(max(date_bin(:interval, date, 2001-01-01)) + interval(:interval) - 1, :to)) AS valueWHERE date >= :from AND date <= :toGROUP BY bucket, typeORDER BY bucket, typereport.account_totals
Section titled “report.account_totals”What every account of one type, such as 'Expenses', changed by in the range, valued in currency at the prices of to, smallest value first: the income and expense breakdowns.
| Parameter | Type | Value |
|---|---|---|
type |
str |
'Assets', 'Liabilities', 'Equity', 'Income' or 'Expenses' |
from |
date |
the first day |
to |
date |
the last day |
currency |
str |
the currency of the values |
SELECT account, units(sum(position)) AS units, convert(sum(position), :currency, :to) AS valueWHERE under(account, :type) AND date >= :from AND date <= :toGROUP BY accountORDER BY number(only(:currency, convert(sum(position), :currency, :to))), accountreport.top_postings
Section titled “report.top_postings”The ten largest postings to accounts of one type in the range, by their value in currency at the prices of to: the top expenses and incomes. possign turns income and liabilities positive, so the largest income comes first. Postings that no price converts to currency come last. account_balance is the balance of the posting’s account right after it, in the posting’s currency.
| Parameter | Type | Value |
|---|---|---|
type |
str |
'Assets', 'Liabilities', 'Equity', 'Income' or 'Expenses' |
from |
date |
the first day |
to |
date |
the last day |
currency |
str |
the currency of the values |
SELECT date, time, timestamp, account, id, payee, narration, units(position) AS units, only(currency, account_balance) AS account_balance, convert(position, :currency, :to) AS valueWHERE under(account, :type) AND date >= :from AND date <= :toORDER BY currency(convert(position, :currency, :to)) = :currency DESC, number(possign(convert(position, :currency, :to), account)) DESCLIMIT 10Accounts
Section titled “Accounts”An account page shows the account and its sub-accounts, as the account tree does: its journal, its balance history, its total and its documents cover the whole subtree. The page of Assets:Bank includes the postings of Assets:Bank:Checking, and its journal names the account of each posting. Balances are valued in the operating currency at today’s prices with convert, which uses inverse prices and the cost currency of a holding.
The balances with sub-accounts add up rows of these queries: the account list and the account page add the rows of accounts.balances or accounts.subtree_balances of an account and of every account under it, as the account tree of the web UI does. To get the total of a subtree yourself, filter with under:
SELECT currency, sum(number) AS unitsWHERE under(account, 'Assets:Bank')GROUP BY currencyThe journal of an account page merges the rows of accounts.journal and accounts.balance_assertions by their seq, the order in which Zhang processed the ledger, newest first. An assertion therefore stands right after the postings its balance includes, wherever Zhang checked it: a balance written with a time after the transactions of its day before that time, a plain balance after a padding written before it, a balance ... with pad after the other balance entries of its time. Its balance is the running balance where it stands, and its trx_id is the id of its check, which GET /api/journals lists it with; a posting’s is the id of its transaction. The rows of one transaction are newest first, by posting, and the rows of a posting booked against several lots are shown as one row. On a day daylight saving skips a time, the time written decides the order, so a row written in the gap can stand before one with an earlier stored time.
The page lists the journal by pages of 100 rows (GET /api/accounts/{account}/journals?page=1&size=100), each row of accounts.journal, a posting, and each assertion counting as one, so every page up to the last one is full. A page reads its postings with accounts.journal_page, from the end of the journal; the number of postings is the total of that query, which POST /api/query returns with count_total.
accounts.list
Section titled “accounts.list”Every account with an open or close directive, with its open and close dates and its alias, by name. With accounts.balances, it makes the account list: an account with postings but no open directive is listed too.
SELECT account, open, close, meta('alias') AS aliasFROM #accountsORDER BY accountaccounts.balances
Section titled “accounts.balances”The balance of every account that has postings, of its own postings, per currency: the units, their value in the operating currency at today’s prices, and the date of the first posting. The account list adds up the rows of an account and of the accounts under it for the balance with sub-accounts.
| Parameter | Type | Value |
|---|---|---|
operating_currency |
str |
the operating_currency option of the ledger |
SELECT account, currency, sum(number) AS units, convert(sum(position), :operating_currency, today()) AS value, min(date) AS first_dateGROUP BY account, currencyORDER BY account, currencyaccounts.subtree
Section titled “accounts.subtree”An account and its sub-accounts that have an open or close directive. The account page reads its dates, status and alias from it.
| Parameter | Type | Value |
|---|---|---|
account |
str |
the account of the page |
SELECT account, open, close, meta('alias') AS aliasFROM #accountsWHERE under(account, :account)ORDER BY accountaccounts.subtree_balances
Section titled “accounts.subtree_balances”The balance of an account and of each of its sub-accounts, as in accounts.balances. The account page shows the total of its rows, the balance that a balance assertion on the account is checked against, and the balance of the account alone.
| Parameter | Type | Value |
|---|---|---|
account |
str |
the account of the page |
operating_currency |
str |
the operating_currency option of the ledger |
SELECT account, currency, sum(number) AS units, convert(sum(position), :operating_currency, today()) AS value, min(date) AS first_dateWHERE under(account, :account)GROUP BY account, currencyORDER BY account, currencyaccounts.journal
Section titled “accounts.journal”The postings of the account and its sub-accounts, in ledger order, a row per posting, each with the running balance of the account and its sub-accounts right after it, in every currency; the page shows that of the posting’s currency. The padding transactions of balance ... with pad are listed like the others. A posting booked against several lots has a row per lot in the postings, which share its seq and posting_index: grouped by those, its lots add up to its units, first() takes its date, payee and the other columns, and last() the balance after its last lot. Add ORDER BY seq DESC, posting_index DESC to list the newest first, as the page does.
| Parameter | Type | Value |
|---|---|---|
account |
str |
the account of the page |
SELECT first(date) AS date, first(time) AS time, first(timestamp) AS timestamp, first(flag) AS flag, first(id) AS id, first(account) AS account, first(payee) AS payee, first(narration) AS narration, seq, posting_index, sum(number) AS units, first(currency) AS currency, last(units(balance)) AS balanceWHERE under(account, :account)GROUP BY seq, posting_indexaccounts.journal_page
Section titled “accounts.journal_page”Some rows of accounts.journal, in ledger order: a page of the journal reads its postings counting from the end. Only the postings of the page are built, whatever the offset, as the lot rows of a posting come one after another.
| Parameter | Type | Value |
|---|---|---|
account |
str |
the account of the page |
limit |
int |
how many postings |
offset |
int |
how many postings before them |
SELECT first(date) AS date, first(time) AS time, first(timestamp) AS timestamp, first(flag) AS flag, first(id) AS id, first(account) AS account, first(payee) AS payee, first(narration) AS narration, seq, posting_index, sum(number) AS units, first(currency) AS currency, last(units(balance)) AS balanceWHERE under(account, :account)GROUP BY seq, posting_indexLIMIT :limit OFFSET :offsetaccounts.balance_assertions
Section titled “accounts.balance_assertions”The balance assertions on the account, newest first by seq. actual is the balance of the account and its sub-accounts that the assertion was checked against, which is the running balance of the journal where its seq puts it. pad is the account a balance ... with pad pads from.
| Parameter | Type | Value |
|---|---|---|
account |
str |
the account of the page |
SELECT date, time, timestamp, id, account, amount, actual, passed, pad, seqFROM #balancesWHERE account = :accountORDER BY seq DESCaccounts.balance_history
Section titled “accounts.balance_history”The balance history chart of an account page: the balance of the account and its sub-accounts at the end of every day with a posting, per currency, in date order.
| Parameter | Type | Value |
|---|---|---|
account |
str |
the account of the page |
SELECT date, currency, last(only(currency, units(balance))) AS balanceWHERE under(account, :account)GROUP BY date, currencyORDER BY date, currencyaccounts.documents
Section titled “accounts.documents”The documents of an account page: the document directives of the account and its sub-accounts, in ledger order. path is the path of the file relative to the ledger’s directory, which the page downloads it with.
| Parameter | Type | Value |
|---|---|---|
account |
str |
the account of the page |
SELECT date, time, account, pathFROM #documentsWHERE source = 'directive' AND under(account, :account)Journals
Section titled “Journals”The journal page (GET /api/journals), the payees and accounts the new-transaction form suggests (GET /api/for-new-transaction), the documents page (GET /api/documents) and the error list (GET /api/errors).
journals.page
Section titled “journals.page”One page of the journal, newest first: the transactions, padding transactions included, and the balance assertions that match a keyword, tags and links, where a NULL parameter leaves its filter out.
| Parameter | Type | Value |
|---|---|---|
keyword |
str |
the search text, or NULL for none: a transaction matches if its payee, narration, tags, links or accounts contain it, ignoring case, and a balance assertion if its accounts or the words Balance Check do. It is plain text, never a regular expression. |
tags |
set |
the tags, any of which a transaction must have, or NULL for any; a balance assertion has none |
links |
set |
the links, any of which a transaction must have, or NULL for any |
size |
int |
the number of rows of a page, from 1 to 1000 |
offset |
int |
the rows before the page: (page - 1) × size |
SELECT seq, type, id, date, time, flag, payee, narration, tags, links, metasFROM #entriesWHERE (type = 'transaction' AND (:tags IS NULL OR intersects(tags, :tags)) AND (:links IS NULL OR intersects(links, :links)) AND (:keyword IS NULL OR icontains(payee, :keyword) OR icontains(narration, :keyword) OR any_icontains(tags, :keyword) OR any_icontains(links, :keyword) OR any_icontains(accounts, :keyword))) OR (type = 'balance' AND :tags IS NULL AND :links IS NULL AND (:keyword IS NULL OR icontains('Balance Check', :keyword) OR any_icontains(accounts, :keyword)))ORDER BY seq DESCLIMIT :size OFFSET :offset- The page counts all its rows before
LIMITandOFFSETfor its number of pages.GET /api/journalsandGET /api/errorstake a pagesizefrom 1 to 1000, 100 by default, and answer another size with HTTP 400 and the messagesize must be between 1 and 1000; a page past the last one is empty. - Rows come newest first, in the processing order: by date and the time written, then at one time the balance entries (balance assertions and every transaction flagged
P) before the other transactions, in the order of your files, with abalance ... with padafter the other balance entries of its time, its padding among them, where Zhang checks it. A balance assertion therefore stands right above the postings its balance includes. On a day daylight saving skips a time, an entry written in the gap keeps its place but shows the time it is stored at, moved forward by the length of the gap:02:30in New York on 2024-03-10 shows as03:30. - A transaction with the flag
Pis a padding transaction, which the page shows as aBalancePaditem. Abalancerow is aBalanceCheckitem, built fromjournals.balance_checks.
journals.postings
Section titled “journals.postings”The postings of some transactions as written, in ledger order, with their units, whether those were inferred, the per-unit costs of their lots and the balance of their account in their currency before and after them.
| Parameter | Type | Value |
|---|---|---|
ids |
set |
the ids of the transactions, those of a page of journals.page |
SELECT id, posting_index, account, automatic, balanced, first(currency) AS currency, sum(number) AS number, count(*) AS lots, count(cost_number) AS lots_at_cost, min(cost_number) AS cost_number, max(cost_number) AS max_cost_number, min(cost_currency) AS cost_currency, max(cost_currency) AS max_cost_currency, number(last(only(currency, account_balance))) - sum(number) AS balance_before, number(last(only(currency, account_balance))) AS balance_after, first(metas) AS metasWHERE id IN :idsGROUP BY id, posting_index, account, automatic, balanced- A posting that lot booking splits into several rows is one row again, its units added up. Its cost is the per-unit cost of its lots, so a
{{1000 USD}}cost of 10 units is100 USD. A reduction booked against lots of different costs has none:cost_numberandmax_cost_number(and the currencies) differ, orlots_at_costis less thanlots. - A posting written without an amount (
automatic) has no units in the journal, only the inferred ones. balance_beforeandbalance_afterare the balance of the posting’s own account, in the posting’s currency, around it:account_balancedoes not depend onWHERE.
journals.balance_checks
Section titled “journals.balance_checks”Some balance assertions with the asserted amount, the account’s true balance, their difference and whether the assertion holds.
| Parameter | Type | Value |
|---|---|---|
ids |
set |
the ids of the assertions, those of a page of journals.page |
SELECT id, account, amount, tolerance, actual, passed, amount - actual AS difference, actual + (amount - actual) AS assertedFROM #balancesWHERE id IN :idsasserted is the asserted amount written with the decimal places of the balance too.
journals.payees
Section titled “journals.payees”Every payee of the ledger’s transactions, once and sorted, without those of the padding transactions.
SELECT DISTINCT payeeFROM #transactionsWHERE payee IS NOT NULL AND payee != '' AND flag != 'P'ORDER BY payeejournals.accounts
Section titled “journals.accounts”The open accounts, sorted by name.
SELECT accountFROM #accountsWHERE open IS NOT NULL AND close IS NULLORDER BY accountjournals.documents
Section titled “journals.documents”Every document of the ledger, newest first: the document directives and the documents that transactions and their postings name in their metadata.
SELECT date, time, path, account, transaction_idFROM #documentsORDER BY seq DESCpath is the path to download the document by, relative to the ledger’s directory. A document named by a posting belongs to the posting’s account.
journals.errors
Section titled “journals.errors”One page of the ledger’s errors, by file and then by position in the file.
| Parameter | Type | Value |
|---|---|---|
size |
int |
the number of errors of a page, from 1 to 1000 |
offset |
int |
the errors before the page: (page - 1) × size |
SELECT id, kind, file, line, column, span_start, span_end, source, metasFROM #errorsLIMIT :size OFFSET :offsetThe error list shows where each error is as its file and the lines of its directive, from line and the lines of source; span_start and span_end are the byte offsets a writer replaces the directive by.
Budgets and commodities
Section titled “Budgets and commodities”The budget pages read #budgets, #budget_definitions and #budget_events. A month is given as its first day, such as 2024-06-01; without one, the pages ask for the current month in the ledger’s timezone.
budgets.month
Section titled “budgets.month”Every budget as of a month: its last month in #budgets up to that month, as the budgets page lists them. #budgets has a row for every month of a budget through the current month, so last_month is the requested month, unless it is a later one. Nothing can have happened to the budget since last_month, so the CASE carries it over: the month starts with available and spends nothing. activity is a number, in the budget’s currency. Budgets that start after the month are not listed. WHERE date <= :month also makes #budgets generate no later month, so a date typo far ahead in the ledger does not get in the way.
| Parameter | Type | Value |
|---|---|---|
month |
date |
the first day of the month |
SELECT name, last(alias) AS alias, last(category) AS category, last(currency) AS currency, last(date) AS last_month, CASE WHEN last(date) < :month THEN last(available) ELSE last(assigned) END AS assigned, CASE WHEN last(date) < :month THEN 0 ELSE number(last(activity)) END AS activity, last(available) AS available, last(closed) AS closedFROM #budgetsWHERE date <= :monthGROUP BY nameORDER BY namebudgets.budget
Section titled “budgets.budget”One budget: its display name, category, commodity, and the accounts whose postings are its activity, from #budget_definitions, which has no months. No row if there is no such budget.
| Parameter | Type | Value |
|---|---|---|
name |
str |
the budget |
SELECT name, alias, category, currency, accountsFROM #budget_definitionsWHERE name = :namebudgets.budget_month
Section titled “budgets.budget_month”One budget as of a month, as in budgets.month. No row if the budget starts after the month; its page then shows nothing assigned or spent.
| Parameter | Type | Value |
|---|---|---|
name |
str |
the budget |
month |
date |
the first day of the month |
SELECT name, last(alias) AS alias, last(category) AS category, last(currency) AS currency, last(date) AS last_month, CASE WHEN last(date) < :month THEN last(available) ELSE last(assigned) END AS assigned, CASE WHEN last(date) < :month THEN 0 ELSE number(last(activity)) END AS activity, last(available) AS available, last(closed) AS closedFROM #budgetsWHERE name = :name AND date <= :monthGROUP BY namebudgets.events
Section titled “budgets.events”What the budget-add and budget-transfer directives put into a budget in a month, newest first, as written: a transfer out is negative.
| Parameter | Type | Value |
|---|---|---|
name |
str |
the budget |
month |
date |
the first day of the month |
SELECT date, time, timestamp, type, amountFROM #budget_eventsWHERE name = :name AND type != 'close' AND yearmonth(date) = :monthORDER BY timestamp DESCbudgets.postings
Section titled “budgets.postings”The postings of a budget in a month, newest first, each with its account’s balance in the posting’s currency after it: those of its accounts that count in it at their date, by account_budgets, so a posting of an account closed and opened again with another budget is listed in the budget it counts in. The budget’s page lists them together with the events of budgets.events, newest first.
| Parameter | Type | Value |
|---|---|---|
accounts |
set |
the budget’s accounts, the accounts of budgets.budget |
month |
date |
the first day of the month |
name |
str |
the budget |
SELECT date, time, timestamp, account, id, payee, narration, units(position) AS units, only(currency, account_balance) AS balanceWHERE account IN :accounts AND yearmonth(date) = :month AND :name IN account_budgets(account, date)ORDER BY timestamp DESCWhat a commodity is (its precision, prefix, suffix, rounding and group) comes from its commodity directive. How much of it the ledger holds, in which lots, and its prices come from the queries below. Holdings are those of the Assets and Liabilities accounts, chosen with under so that the query reads only their postings.
commodities.totals
Section titled “commodities.totals”How many units of each commodity the Assets and Liabilities accounts hold, for the commodities they hold. A commodity without a row holds nothing.
SELECT currency, sum(number) AS totalWHERE under(account, 'Assets') OR under(account, 'Liabilities')GROUP BY currencyHAVING sum(number) != 0ORDER BY currencycommodities.total
Section titled “commodities.total”How many units of one commodity the Assets and Liabilities accounts hold. No row if they hold none.
| Parameter | Type | Value |
|---|---|---|
commodity |
str |
the commodity |
SELECT currency, sum(number) AS totalWHERE currency = :commodity AND (under(account, 'Assets') OR under(account, 'Liabilities'))GROUP BY currencyHAVING sum(number) != 0commodities.latest_prices
Section titled “commodities.latest_prices”The latest price of each commodity in a currency as of today, with its date and time. The rate is the one the valuations use, from the engine’s price map: the latest quote of the pair on or before today, in either direction, inverted when it is quoted the other way round. A price dated in the future is not the latest price.
| Parameter | Type | Value |
|---|---|---|
currency |
str |
the currency of the prices, the operating currency |
SELECT CASE WHEN currency = :currency THEN currency(amount) ELSE currency END AS currency, last(date) AS date, last(time) AS time, getprice(last(CASE WHEN currency = :currency THEN currency(amount) ELSE currency END), :currency, today()) AS rateFROM #pricesWHERE (currency = :currency OR currency(amount) = :currency) AND currency != currency(amount) AND date <= today()GROUP BY 1ORDER BY 1commodities.latest_price
Section titled “commodities.latest_price”The latest price of one commodity in a currency as of today, with its date and time. The rate is the one the valuations use, from the engine’s price map: the latest quote of the pair on or before today, in either direction, inverted when it is quoted the other way round. A price dated in the future is not the latest price.
| Parameter | Type | Value |
|---|---|---|
commodity |
str |
the commodity |
currency |
str |
the currency of the price, the operating currency |
SELECT CASE WHEN currency = :currency THEN currency(amount) ELSE currency END AS currency, last(date) AS date, last(time) AS time, getprice(:commodity, :currency, today()) AS rateFROM #pricesWHERE ((currency = :commodity AND currency(amount) = :currency) OR (currency = :currency AND currency(amount) = :commodity)) AND currency != currency(amount) AND date <= today()GROUP BY 1commodities.lots
Section titled “commodities.lots”The lots of a commodity that the Assets and Liabilities accounts hold: the units per account, cost and acquisition date, by account, then oldest first. Lots differing only by label are kept apart. Units held without a cost are one lot per account.
| Parameter | Type | Value |
|---|---|---|
commodity |
str |
the commodity |
SELECT account, cost_date, cost_number, cost_currency, cost_label, sum(number) AS unitsWHERE currency = :commodity AND (under(account, 'Assets') OR under(account, 'Liabilities'))GROUP BY account, cost_date, cost_number, cost_currency, cost_labelHAVING sum(number) != 0ORDER BY account, cost_date, cost_number, cost_labelcommodities.prices
Section titled “commodities.prices”Every price of a commodity, in any currency, oldest first.
| Parameter | Type | Value |
|---|---|---|
commodity |
str |
the commodity |
SELECT date, time, amountFROM #pricesWHERE currency = :commodityORDER BY date, time