Tahlia Trading ledger
Tahlia Trading Company (fictional) · 11 tables · 19,173 rows · version 1
Tahlia Trading Company is a fictional distributor of household goods, food and electronics with sales teams in Riyadh, Jeddah and Dammam and an online store. The dataset holds its chart of accounts, cost centers, monthly fiscal periods (the fiscal year is the calendar year), every journal entry and line for 2024 and 2025, monthly budgets, its suppliers, their invoices and payments, and the operating bank account's statement. Use it for trial balances, monthly P&L, budget vs actual, variance analysis, AP ageing and bank reconciliation.
The documentation is open to everyone. Querying this dataset in the playground is included with Formula Quest membership; lessons that use it follow their own access.
Relationships
Sample queries
SELECT l.account_code, a.account_name, a.account_type,
SUM(l.debit) AS total_debit, SUM(l.credit) AS total_credit,
SUM(l.debit - l.credit) AS net_debit
FROM journal_lines AS l
JOIN journal_entries AS e ON e.entry_id = l.entry_id
JOIN gl_accounts AS a ON a.account_code = l.account_code
WHERE e.status = 'posted'
GROUP BY l.account_code, a.account_name, a.account_type
ORDER BY l.account_code;SELECT e.period_id,
SUM(CASE WHEN a.account_type = 'revenue' THEN l.credit - l.debit ELSE 0 END) AS revenue,
SUM(CASE WHEN a.account_type = 'expense' THEN l.debit - l.credit ELSE 0 END) AS expenses,
SUM(CASE WHEN a.account_type = 'revenue' THEN l.credit - l.debit
WHEN a.account_type = 'expense' THEN l.credit - l.debit ELSE 0 END) AS net_profit
FROM journal_lines AS l
JOIN journal_entries AS e ON e.entry_id = l.entry_id
JOIN gl_accounts AS a ON a.account_code = l.account_code
WHERE e.status = 'posted'
GROUP BY e.period_id
ORDER BY e.period_id;WITH actual AS (
SELECT l.cost_center_code, SUM(l.debit - l.credit) AS actual
FROM journal_lines l
JOIN journal_entries e ON e.entry_id = l.entry_id
JOIN gl_accounts a ON a.account_code = l.account_code
JOIN fiscal_periods p ON p.period_id = e.period_id
WHERE e.status = 'posted' AND a.account_type = 'expense' AND p.fiscal_year = 2025
GROUP BY l.cost_center_code
), budget AS (
SELECT b.cost_center_code, SUM(b.amount) AS budget
FROM budgets b JOIN gl_accounts a ON a.account_code = b.account_code
WHERE b.fiscal_year = 2025 AND a.account_type = 'expense'
GROUP BY b.cost_center_code
)
SELECT COALESCE(a.cost_center_code, b.cost_center_code) AS cost_center_code,
b.budget, a.actual, a.actual - b.budget AS variance
FROM actual a FULL JOIN budget b ON b.cost_center_code = a.cost_center_code
ORDER BY 1;SELECT v.vendor_name, i.invoice_number, i.due_date, i.amount,
i.amount - COALESCE(SUM(p.amount), 0) AS outstanding
FROM vendor_invoices AS i
JOIN vendors AS v ON v.vendor_id = i.vendor_id
LEFT JOIN vendor_payments AS p ON p.invoice_id = i.invoice_id
WHERE i.status <> 'paid'
GROUP BY v.vendor_name, i.invoice_id, i.invoice_number, i.due_date, i.amount
ORDER BY i.due_date;SELECT s.statement_date, s.description, s.amount, s.bank_reference
FROM bank_statement_lines AS s
LEFT JOIN vendor_payments AS p ON p.bank_reference = s.bank_reference
WHERE s.amount < 0 AND p.payment_id IS NULL
ORDER BY s.statement_date;Data dictionary
gl_accounts
45 rows · primary key (account_code)Chart of accounts. Header accounts group the postable accounts beneath them (parent_account_code).
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| account_code | text | no | PK | Four-digit account code. The first digit gives the class: 1 assets, 2 liabilities, 3 equity, 4 revenue, 5 cost of sales, 6 operating expenses. |
| account_name | text | no | Account name. | |
| account_type | text | no | asset, liability, equity, revenue or expense (cost of sales counts as expense). Use this to sign amounts in reports. | |
| normal_balance | text | no | The side on which the account normally has its balance. Contra accounts (1590, 4900) are the opposite of their type. | |
| parent_account_code | text | yes | gl_accounts.account_code | Header account this account rolls up to. NULL for the top-level headers. |
| is_postable | boolean | no | TRUE if journal lines may be posted to the account; FALSE for header (grouping) accounts. | |
| is_active | boolean | no | FALSE for accounts retired from use (history remains). |
cost_centers
10 rows · primary key (cost_center_code)Departments and sales teams that income and expenses are charged to.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| cost_center_code | text | no | PK | Cost center code, e.g. 'CC-110'. |
| cost_center_name | text | no | Cost center name. | |
| department | text | no | Department the cost center belongs to. | |
| manager_name | text | no | Budget holder (fictional). | |
| is_active | boolean | no | FALSE for cost centers that have been closed (Sales - Dammam closed on 2025-06-30). |
fiscal_periods
24 rows · primary key (period_id)Monthly accounting periods. The fiscal year is the calendar year; FY2024 and FY2025 have 12 periods each.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| period_id | integer | no | PK | Period key as YYYYMM, e.g. 202503 for March 2025. |
| fiscal_year | integer | no | Fiscal year (2024 or 2025). | |
| period_number | integer | no | Month of the fiscal year, 1-12. | |
| start_date | date | no | First day of the period. | |
| end_date | date | no | Last day of the period. | |
| is_closed | boolean | no | TRUE once the period has been closed for posting. Only December 2025 is still open. |
journal_entries
3,303 rows · primary key (entry_id)Journal entry headers. Each entry has two or more lines in journal_lines. Only 'posted' entries count in the books.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| entry_id | integer | no | PK | Unique number for each entry (in date order). |
| entry_number | text | no | unique | Human-readable number, e.g. 'JE-2025-00042'. |
| entry_date | date | no | Date the entry was made. Usually inside its period, but late adjustments are dated after the period ends. | |
| period_id | integer | no | fiscal_periods.period_id | Accounting period the entry belongs to. Use this, not entry_date, for period reporting. |
| source | text | no | Where the entry came from: AP (payables), AR (sales and receipts), PAYROLL, BANK (bank charges, taxes, transfers), MANUAL (journals typed by accountants) or ADJ (month-end and audit adjustments). | |
| reference | text | yes | Document reference: sales invoice, vendor invoice number, or the bank reference for cash movements. NULL when there is none. | |
| description | text | no | What the entry is for. | |
| status | text | no | 'posted' (final) or 'draft' (not yet approved; may not balance). | |
| reversal_of_entry_id | integer | yes | journal_entries.entry_id | For a reversing entry: the entry it cancels. NULL otherwise. |
journal_lines
11,205 rows · primary key (entry_id, line_no)Debit and credit lines of each journal entry. Exactly one of debit or credit is greater than zero on each line.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| entry_id | integer | no | PK journal_entries.entry_id | The entry this line belongs to. |
| line_no | integer | no | PK | Line number within the entry (1, 2, ...). |
| account_code | text | no | gl_accounts.account_code | Account debited or credited. |
| cost_center_code | text | yes | cost_centers.cost_center_code | Cost center for revenue and expense lines. NULL on balance sheet lines (and on a few expense lines). |
| debit | numeric(14,2) | no | Debit amount in SAR (0 if this is a credit line). | |
| credit | numeric(14,2) | no | Credit amount in SAR (0 if this is a debit line). |
budgets
1,614 rows · primary key (fiscal_year, period_number, account_code, cost_center_code)Monthly budget per account and cost center for revenue and expense accounts. Amounts are positive in the account's natural direction.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| fiscal_year | integer | no | PK | Fiscal year. |
| period_number | integer | no | PK | Month of the fiscal year, 1-12. |
| account_code | text | no | PK gl_accounts.account_code | Revenue or expense account. |
| cost_center_code | text | no | PK cost_centers.cost_center_code | Cost center the budget belongs to. |
| amount | numeric(14,2) | no | Budgeted amount in SAR: expected revenue for revenue accounts, expected cost for expense accounts. |
vendors
25 rows · primary key (vendor_id)Suppliers the company buys from.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| vendor_id | integer | no | PK | Unique number for each vendor record. |
| vendor_name | text | no | Vendor's trading name as entered (the same supplier can exist twice under slightly different names). | |
| category | text | no | What the vendor supplies: inventory, logistics, landlord, utilities, marketing, it, professional, office, travel, hospitality, assets. | |
| city | text | no | Vendor's city. | |
| country | text | no | Vendor's country. Foreign vendors charge no Saudi VAT and are paid by SWIFT. | |
| payment_terms_days | integer | no | Days after the invoice date by which payment is due. | |
| is_active | boolean | no | FALSE for vendors no longer used. |
vendor_invoices
682 rows · primary key (invoice_id)Supplier invoices (accounts payable sub-ledger). entry_id links to the AP journal entry that booked the invoice.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| invoice_id | integer | no | PK | Unique number for each invoice record. |
| vendor_id | integer | no | vendors.vendor_id | Supplier who issued the invoice. |
| invoice_number | text | no | The supplier's own invoice number. Unique per vendor only: different vendors reuse the same numbers. | |
| invoice_date | date | no | Date on the invoice. | |
| due_date | date | no | Date payment is due (invoice date + the vendor's payment terms). | |
| net_amount | numeric(14,2) | no | Amount before VAT in SAR. | |
| vat_amount | numeric(14,2) | no | VAT at 15% in SAR (0 for foreign suppliers). | |
| amount | numeric(14,2) | no | Total payable in SAR = net_amount + vat_amount. | |
| status | text | no | paid (fully paid), open (not or only partly paid) or disputed (on hold). | |
| entry_id | integer | yes | journal_entries.entry_id | Journal entry that booked the invoice. NULL if not booked yet (disputed, or received after the year-end cut-off). Invoices from before 2024 point to the opening-balance entry. |
vendor_payments
671 rows · primary key (payment_id)Payments made to suppliers from the operating bank account. A payment settles one invoice, part of one, or none (an advance).
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| payment_id | integer | no | PK | Unique number for each payment. |
| vendor_id | integer | no | vendors.vendor_id | Supplier paid. |
| invoice_id | integer | yes | vendor_invoices.invoice_id | Invoice the payment is applied to. NULL for an unapplied advance payment. |
| payment_date | date | no | Date the payment was issued and recorded in the ledger. | |
| amount | numeric(14,2) | no | Amount paid in SAR. | |
| method | text | no | bank_transfer, cheque, sadad (bill payment) or swift (international transfer). | |
| bank_reference | text | no | unique | Reference that appears on the bank statement when the payment clears. Also stored as journal_entries.reference. |
| entry_id | integer | no | journal_entries.entry_id | Journal entry that recorded the payment. |
bank_accounts
1 rows · primary key (bank_account_id)Bank accounts whose statements are loaded. Only the operating account (GL 1110) is reconciled here.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| bank_account_id | integer | no | PK | Unique number for each bank account. |
| account_name | text | no | Internal name of the account. | |
| bank_name | text | no | Bank holding the account (fictional). | |
| iban_masked | text | no | Masked, synthetic IBAN. | |
| gl_account_code | text | no | gl_accounts.account_code | Ledger account that mirrors this bank account. |
| opening_balance | numeric(14,2) | no | Statement balance in SAR at the start of 2024-01-01 (equal to the ledger's opening balance: nothing was outstanding). |
bank_statement_lines
1,593 rows · primary key (statement_line_id)Lines from the operating account's bank statement, 2024-01-01 to 2025-12-31. Signed from the bank's view: positive = money in, negative = money out.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| statement_line_id | integer | no | PK | Unique number for each statement line. |
| bank_account_id | integer | no | bank_accounts.bank_account_id | Bank account the line belongs to. |
| statement_date | date | no | Date the bank booked the line. | |
| value_date | date | no | Date the money actually moved (value date). | |
| description | text | no | Bank's narrative, in capitals. | |
| amount | numeric(14,2) | no | Signed amount in SAR: positive = deposit, negative = withdrawal. | |
| bank_reference | text | yes | Reference to match against vendor_payments.bank_reference or journal_entries.reference. NULL for bank charges. |