hledger and SQLite
With SQLite you can do full relational queries on your hledger data.
Here we export all transactions to a database and run some queries:
$ hledger -f examples/bcexample.hledger print -O sql | sqlite3 bcexample.db
$ sqlite3 bcexample.db -column -header
SQLite version 3.37.0 2021-12-09 01:34:53
Enter ".help" for usage hints.
sqlite> .tables
postings
sqlite> .schema
CREATE TABLE postings(id serial,txnidx int,date1 date,date2 date,status text,code text,description text,comment text,account text,amount numeric,commodity text,credit numeric,debit numeric,posting_status text,posting_comment text);
sqlite> select count(*) from postings;
count(*)
--------
3203
sqlite> select distinct txnidx,description,abs(amount) from postings order by abs(amount) desc limit 5;
txnidx description abs(amount)
------ ------------------------------------------------ -----------
1031 Allowed contributions for one year 17500
1035 Allowed contributions for one year 17500
1027 Allowed contributions for one year 17000
142 Transfering accumulated savings to other account 5000
954 Hoogle | Payroll 4615.38
sqlite> sqlite> select * from postings where txnidx=954;
id txnidx date1 date2 status code description comment account amount commodity credit debit posting_status posting_comment
-- ------ ---------- ----- ------ ---- ---------------- ------- ------------------------------------------ -------- --------- ------- ------- -------------- ---------------
954 2012-01-05 * Hoogle | Payroll Assets:US:BofA:Checking 1350.6 USD 1350.6
954 2012-01-05 * Hoogle | Payroll Assets:US:Vanguard:Cash 1200 USD 1200
954 2012-01-05 * Hoogle | Payroll Assets:US:Federal:PreTax401k -1200 IRAUSD 1200
954 2012-01-05 * Hoogle | Payroll Expenses:Taxes:Y2012:US:Federal:PreTax401k 1200 IRAUSD 1200
954 2012-01-05 * Hoogle | Payroll Income:US:Hoogle:Salary -4615.38 USD 4615.38
954 2012-01-05 * Hoogle | Payroll Income:US:Hoogle:GroupTermLife -24.32 USD 24.32
954 2012-01-05 * Hoogle | Payroll Expenses:Health:Life:GroupTermLife 24.32 USD 24.32
954 2012-01-05 * Hoogle | Payroll Expenses:Health:Dental:Insurance 2.9 USD 2.9
954 2012-01-05 * Hoogle | Payroll Expenses:Health:Medical:Insurance 27.38 USD 27.38
954 2012-01-05 * Hoogle | Payroll Expenses:Health:Vision:Insurance 42.3 USD 42.3
954 2012-01-05 * Hoogle | Payroll Expenses:Taxes:Y2012:US:Medicare 106.62 USD 106.62
954 2012-01-05 * Hoogle | Payroll Expenses:Taxes:Y2012:US:Federal 1062.92 USD 1062.92
954 2012-01-05 * Hoogle | Payroll Expenses:Taxes:Y2012:US:State 365.08 USD 365.08
954 2012-01-05 * Hoogle | Payroll Expenses:Taxes:Y2012:US:CityNYC 174.92 USD 174.92
954 2012-01-05 * Hoogle | Payroll Expenses:Taxes:Y2012:US:SDI 1.12 USD 1.12
954 2012-01-05 * Hoogle | Payroll Expenses:Taxes:Y2012:US:SocSec 281.54 USD 281.54
954 2012-01-05 * Hoogle | Payroll Assets:US:Hoogle:Vacation 4.62 VACHR 4.62
954 2012-01-05 * Hoogle | Payroll Income:US:Hoogle:Vacation -4.62 VACHR 4.62
sqlite>
You can avoid creating a .db file, always querying the latest journal file instead:
$ (hledger print -O sql; echo "select * from postings where account like 'liabilities%' and amount > 0") | sqlite3 -column -header
Here's a shell function to make that convenient:
$ hq() { (hledger print -O sql; echo "$1") | sqlite3 -column -header; }
Example queries:
$ hq "select distinct(account) from postings order by account"
$ hq "select * from postings where account like '%savings%' and amount > 0"
The txnidx
field connects postings belonging to the same transaction.
Using this, we can query transactions, and in more complex ways than hledger can.
Here's an example where we want to see just the transfers from Checking to Liabilities.
This is hard to do accurately with hledger's CLI(1):
$ echo; hq "select * from postings where txnidx in \
(select txnidx from postings where account regexp 'Liabilities' and amount > 0 and txnidx in \
(select txnidx from postings where account regexp 'Checking' and amount < 0))"
> >
id txnidx date1 date2 status code description comment account amount commodity credit debit posting_status posting_comment
-- ------ ---------- ----- ------ ---- ------------------------------------ ------- --------------------------- ------- --------- ------ ------ -------------- ---------------
147 2012-01-08 * Chase:Slate | Paying off credit card Liabilities:US:Chase:Slate 140.36 USD 140.36
147 2012-01-08 * Chase:Slate | Paying off credit card Assets:US:BofA:Checking -140.36 USD 140.36
163 2012-02-11 * Chase:Slate | Paying off credit card Liabilities:US:Chase:Slate 725.96 USD 725.96
163 2012-02-11 * Chase:Slate | Paying off credit card Assets:US:BofA:Checking -725.96 USD 725.96
177 2012-03-09 * Chase:Slate | Paying off credit card Liabilities:US:Chase:Slate 580 USD 580
177 2012-03-09 * Chase:Slate | Paying off credit card Assets:US:BofA:Checking -580 USD 580
...
(1. Well.. it's not that hard to get a decent result given typical data patterns:
$ hledger -f examples/bcexample.hledger print Checking | hledger -f- areg -w80 Liabilities amt:'>0'
Transactions in Liabilities and subaccounts:
2012-01-08 Chase:Slate | Pay.. Li:US:Ch:Slate 140.36 USD 140.36 USD
2012-02-11 Chase:Slate | Pay.. Li:US:Ch:Slate 725.96 USD 866.32 USD
2012-03-09 Chase:Slate | Pay.. Li:US:Ch:Slate 580.00 USD 1446.32 USD
...
but in general, this is not as robust as the relational query.)
Only hledger's print
command outputs SQL.
But many hledger reports can produce CSV, and you can also run SQLite on that:
or on hledger's JSON output:
See also hledger and dsq / DataStation.
And Michael Peter: My plain text accounting workflow with hledger. This recommends the following export command to create a useful primary key:
hledger print -O sql | sed 's/id serial/id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL/g' | sqlite3 ledger.db
(#2017)