Files
grist-accounting/references/queries.md
Bill Ballou 5693d7b451 Add year-end tax report queries for period-specific reporting
Adds date-parameterized SQL queries for Income Statement, Expense
Detail, Vendor Payment Summary, and General Ledger. These filter
TransactionLines by date range instead of using cumulative balances,
enabling tax year and other period-specific financial reports.
2026-02-22 18:26:19 -05:00

7.4 KiB

SQL Queries and Reports

Common queries and financial report templates.

Contents

Common Queries

Unpaid Bills by Vendor

SELECT v.Name, b.BillNumber, b.BillDate, b.Amount, b.AmountDue
FROM Bills b
JOIN Vendors v ON b.Vendor = v.id
WHERE b.Status IN ('Open', 'Partial')
ORDER BY b.DueDate

Bills Summary by Vendor

SELECT v.Name as Vendor, COUNT(b.id) as Bills, SUM(b.Amount) as Total, SUM(b.AmountDue) as Due
FROM Bills b
JOIN Vendors v ON b.Vendor = v.id
GROUP BY v.Name
ORDER BY Total DESC

Account Balances (Non-Zero)

SELECT Code, Name, Type, Balance
FROM Accounts
WHERE Balance != 0
ORDER BY Code

Owner Reimbursement Balance

SELECT Balance FROM Accounts WHERE Code = '2203'

Expense Summary by Account

SELECT a.Code, a.Name, a.Balance
FROM Accounts a
WHERE a.Type = 'Expense' AND a.Balance != 0
ORDER BY a.Balance DESC

Transaction History for Account

SELECT t.Date, t.Description, t.Reference, tl.Debit, tl.Credit
FROM TransactionLines tl
JOIN Transactions t ON tl.Transaction = t.id
WHERE tl.Account = {account_id}
ORDER BY t.Date DESC

Verify All Transactions Balance

SELECT id, Description, Total, IsBalanced
FROM Transactions
WHERE IsBalanced = false

Financial Reports

Balance Sheet

Shows Assets = Liabilities + Equity at a point in time.

Important: Parent accounts roll up child balances. Query only top-level parents (Parent = 0) to avoid double-counting.

-- Assets, Liabilities, Equity (top-level only)
SELECT Code, Name, Type, Balance
FROM Accounts
WHERE Type IN ('Asset', 'Liability', 'Equity')
  AND Parent = 0
ORDER BY Type, Code
-- Net Income (for Equity section)
SELECT
  COALESCE(SUM(CASE WHEN Type = 'Income' THEN Balance ELSE 0 END), 0) -
  COALESCE(SUM(CASE WHEN Type = 'Expense' THEN Balance ELSE 0 END), 0) as NetIncome
FROM Accounts
WHERE Type IN ('Income', 'Expense')
  AND id NOT IN (SELECT DISTINCT Parent FROM Accounts WHERE Parent != 0)

Presentation:

Assets
Cash & Bank Accounts $X.XX
Accounts Receivable $X.XX
Total Assets $X.XX
Liabilities
Accounts Payable $X.XX
Due to Owner $X.XX
Total Liabilities $X.XX
Equity
Retained Earnings $X.XX
Net Income (Loss) $X.XX
Total Equity $X.XX

| Total Liabilities + Equity | $X.XX |

Income Statement

Shows Revenue - Expenses = Net Income for a period.

SELECT Code, Name, Type, Balance
FROM Accounts
WHERE Type IN ('Income', 'Expense')
  AND Balance != 0
  AND id NOT IN (SELECT DISTINCT Parent FROM Accounts WHERE Parent != 0)
ORDER BY Type DESC, Code

Presentation:

Income
Service Revenue $X.XX
Total Income $X.XX
Expenses
Software & Subscriptions $X.XX
Professional Services $X.XX
Total Expenses $X.XX

| Net Income (Loss) | $X.XX |

Trial Balance

Lists all accounts with non-zero balances. Debits should equal Credits.

SELECT
  Code,
  Name,
  Type,
  CASE WHEN Type IN ('Asset', 'Expense') THEN Balance ELSE 0 END as Debit,
  CASE WHEN Type IN ('Liability', 'Equity', 'Income') THEN Balance ELSE 0 END as Credit
FROM Accounts
WHERE Balance != 0
  AND id NOT IN (SELECT DISTINCT Parent FROM Accounts WHERE Parent != 0)
ORDER BY Code

Accounts Payable Aging

SELECT
  v.Name as Vendor,
  b.BillNumber,
  b.BillDate,
  b.DueDate,
  b.AmountDue,
  CASE
    WHEN b.DueDate >= strftime('%s', 'now') THEN 'Current'
    WHEN b.DueDate >= strftime('%s', 'now') - 2592000 THEN '1-30 Days'
    WHEN b.DueDate >= strftime('%s', 'now') - 5184000 THEN '31-60 Days'
    ELSE '60+ Days'
  END as Aging
FROM Bills b
JOIN Vendors v ON b.Vendor = v.id
WHERE b.Status IN ('Open', 'Partial')
ORDER BY b.DueDate

Year-End Tax Reports

Date-parameterized queries for tax year reporting. Replace {start_date} and {end_date} with Unix timestamps (e.g., 2025: start=1735776000, end=1767312000).

Period Income Statement (P&L)

Sums TransactionLines by Income/Expense account for a date range (Schedule C core data):

SELECT a.Code, a.Name, a.Type,
  SUM(tl.Debit) as Debits, SUM(tl.Credit) as Credits
FROM TransactionLines tl
JOIN Transactions t ON tl.Transaction = t.id
JOIN Accounts a ON tl.Account = a.id
WHERE a.Type IN ('Income', 'Expense')
  AND t.Date >= {start_date} AND t.Date < {end_date}
GROUP BY a.Code, a.Name, a.Type
ORDER BY a.Type DESC, a.Code

Period Expense Detail

All expense transactions with individual line items, grouped by account:

SELECT t.Date, a.Code, a.Name, t.Description, t.Reference, tl.Debit, tl.Credit
FROM TransactionLines tl
JOIN Transactions t ON tl.Transaction = t.id
JOIN Accounts a ON tl.Account = a.id
WHERE a.Type = 'Expense'
  AND t.Date >= {start_date} AND t.Date < {end_date}
ORDER BY a.Code, t.Date

Vendor Payment Summary

Total payments per vendor for 1099 determination ($600+ threshold):

SELECT v.Name as Vendor, COUNT(bp.id) as Payments, SUM(bp.Amount) as TotalPaid
FROM BillPayments bp
JOIN Bills b ON bp.Bill = b.id
JOIN Vendors v ON b.Vendor = v.id
WHERE bp.PaymentDate >= {start_date} AND bp.PaymentDate < {end_date}
GROUP BY v.Name
ORDER BY TotalPaid DESC

Period General Ledger

All transactions in the period with full detail:

SELECT t.Date, t.Description, t.Reference, t.Status,
  a.Code, a.Name, tl.Debit, tl.Credit, tl.Memo
FROM TransactionLines tl
JOIN Transactions t ON tl.Transaction = t.id
JOIN Accounts a ON tl.Account = a.id
WHERE t.Date >= {start_date} AND t.Date < {end_date}
ORDER BY t.Date, t.id, a.Code

Point-in-Time Balance Sheet / Trial Balance

Use Accounts.Balance (cumulative) for businesses that started tracking in Grist during the reporting year. If prior-year data exists, subtract beginning-of-year balances. See the Financial Reports section for balance queries.

Note

: If tl.Transaction JOIN fails due to the reserved word issue, fall back to get_records on TransactionLines + programmatic joining.

Reconciliation Queries

Cleared Balance for Bank Account

Use get_records to avoid the Transaction reserved word issue:

# Step 1: Get all TransactionLines for Checking (id=14)
lines = get_records("TransactionLines", filter={"Account": [14]})

# Step 2: Get cleared transaction IDs
txn_ids = list(set(line["Transaction"] for line in lines))
cleared = sql_query(f"SELECT id FROM Transactions WHERE Status = 'Cleared' AND id IN (...)")
cleared_ids = set(row["id"] for row in cleared)

# Step 3: Sum Debit - Credit for cleared lines
cleared_balance = sum(
    line["Debit"] - line["Credit"]
    for line in lines
    if line["Transaction"] in cleared_ids
)

Outstanding Items (Not Cleared by Bank)

SELECT t.id, t.Date, t.Description, t.Status,
       tl.Debit, tl.Credit
FROM TransactionLines tl
JOIN Transactions t ON tl.Transaction = t.id
WHERE tl.Account = 14
  AND t.Status != 'Cleared'
ORDER BY t.Date

Reconciliation History

SELECT id, StatementDate, StatementBalance,
       ClearedBalance, Difference, Status
FROM Reconciliations
WHERE Account = 14
ORDER BY StatementDate DESC

Active Bank Rules

get_records("BankRules", filter={"Account": [14], "IsActive": [true]})