SQL Quest

Lessons, hints, explained solutions, the in-browser PostgreSQL editor and practice answer checks in your browser are available. Graded assessments, recorded progress from answer checks and new certificates aren't available yet.

Tahlia Trading ledger

Tahlia Trading Company (fictional) · 11 tables · 19,173 rows · version 1

Open in the playground

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

gl_accounts45PKaccount_codeFKparent_account_codeaccount_nameaccount_typenormal_balanceis_postableis_activecost_centers10PKcost_center_codecost_center_namedepartmentmanager_nameis_activefiscal_periods24PKperiod_idfiscal_yearperiod_numberstart_dateend_dateis_closedjournal_entries3,303PKentry_idFKperiod_idFKreversal_of_entry_identry_numberentry_datesourcereferencedescriptionstatusjournal_lines11,205PKentry_idPKline_noFKaccount_codeFKcost_center_codedebitcreditbudgets1,614PKfiscal_yearPKperiod_numberPKaccount_codePKcost_center_codeamountvendors25PKvendor_idvendor_namecategorycitycountrypayment_terms_daysis_activevendor_invoices682PKinvoice_idFKvendor_idFKentry_idinvoice_numberinvoice_datedue_datenet_amountvat_amountamount+ 1 more columnsvendor_payments671PKpayment_idFKvendor_idFKinvoice_idFKentry_idpayment_dateamountmethodbank_referencebank_accounts1PKbank_account_idFKgl_account_codeaccount_namebank_nameiban_maskedopening_balancebank_statement_lines1,593PKstatement_line_idFKbank_account_idstatement_datevalue_datedescriptionamountbank_reference
Arrows point from a foreign key to the table it references. PK = primary key, FK = foreign key. Numbers are row counts.

Sample queries

Trial balance at 31 December 2025
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;
Monthly profit and loss
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;
Budget vs actual by cost center, FY2025
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;
Open payables by vendor
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;
Bank lines with no matching vendor payment
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).

ColumnTypeNull?KeyDescription
account_codetextno PKFour-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_nametextnoAccount name.
account_typetextnoasset, liability, equity, revenue or expense (cost of sales counts as expense). Use this to sign amounts in reports.
normal_balancetextnoThe side on which the account normally has its balance. Contra accounts (1590, 4900) are the opposite of their type.
parent_account_codetextyes gl_accounts.account_codeHeader account this account rolls up to. NULL for the top-level headers.
is_postablebooleannoTRUE if journal lines may be posted to the account; FALSE for header (grouping) accounts.
is_activebooleannoFALSE 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.

ColumnTypeNull?KeyDescription
cost_center_codetextno PKCost center code, e.g. 'CC-110'.
cost_center_nametextnoCost center name.
departmenttextnoDepartment the cost center belongs to.
manager_nametextnoBudget holder (fictional).
is_activebooleannoFALSE 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.

ColumnTypeNull?KeyDescription
period_idintegerno PKPeriod key as YYYYMM, e.g. 202503 for March 2025.
fiscal_yearintegernoFiscal year (2024 or 2025).
period_numberintegernoMonth of the fiscal year, 1-12.
start_datedatenoFirst day of the period.
end_datedatenoLast day of the period.
is_closedbooleannoTRUE 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.

ColumnTypeNull?KeyDescription
entry_idintegerno PKUnique number for each entry (in date order).
entry_numbertextnouniqueHuman-readable number, e.g. 'JE-2025-00042'.
entry_datedatenoDate the entry was made. Usually inside its period, but late adjustments are dated after the period ends.
period_idintegerno fiscal_periods.period_idAccounting period the entry belongs to. Use this, not entry_date, for period reporting.
sourcetextnoWhere 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).
referencetextyesDocument reference: sales invoice, vendor invoice number, or the bank reference for cash movements. NULL when there is none.
descriptiontextnoWhat the entry is for.
statustextno'posted' (final) or 'draft' (not yet approved; may not balance).
reversal_of_entry_idintegeryes journal_entries.entry_idFor 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.

ColumnTypeNull?KeyDescription
entry_idintegerno PK journal_entries.entry_idThe entry this line belongs to.
line_nointegerno PKLine number within the entry (1, 2, ...).
account_codetextno gl_accounts.account_codeAccount debited or credited.
cost_center_codetextyes cost_centers.cost_center_codeCost center for revenue and expense lines. NULL on balance sheet lines (and on a few expense lines).
debitnumeric(14,2)noDebit amount in SAR (0 if this is a credit line).
creditnumeric(14,2)noCredit 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.

ColumnTypeNull?KeyDescription
fiscal_yearintegerno PKFiscal year.
period_numberintegerno PKMonth of the fiscal year, 1-12.
account_codetextno PK gl_accounts.account_codeRevenue or expense account.
cost_center_codetextno PK cost_centers.cost_center_codeCost center the budget belongs to.
amountnumeric(14,2)noBudgeted 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.

ColumnTypeNull?KeyDescription
vendor_idintegerno PKUnique number for each vendor record.
vendor_nametextnoVendor's trading name as entered (the same supplier can exist twice under slightly different names).
categorytextnoWhat the vendor supplies: inventory, logistics, landlord, utilities, marketing, it, professional, office, travel, hospitality, assets.
citytextnoVendor's city.
countrytextnoVendor's country. Foreign vendors charge no Saudi VAT and are paid by SWIFT.
payment_terms_daysintegernoDays after the invoice date by which payment is due.
is_activebooleannoFALSE 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.

ColumnTypeNull?KeyDescription
invoice_idintegerno PKUnique number for each invoice record.
vendor_idintegerno vendors.vendor_idSupplier who issued the invoice.
invoice_numbertextnoThe supplier's own invoice number. Unique per vendor only: different vendors reuse the same numbers.
invoice_datedatenoDate on the invoice.
due_datedatenoDate payment is due (invoice date + the vendor's payment terms).
net_amountnumeric(14,2)noAmount before VAT in SAR.
vat_amountnumeric(14,2)noVAT at 15% in SAR (0 for foreign suppliers).
amountnumeric(14,2)noTotal payable in SAR = net_amount + vat_amount.
statustextnopaid (fully paid), open (not or only partly paid) or disputed (on hold).
entry_idintegeryes journal_entries.entry_idJournal 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).

ColumnTypeNull?KeyDescription
payment_idintegerno PKUnique number for each payment.
vendor_idintegerno vendors.vendor_idSupplier paid.
invoice_idintegeryes vendor_invoices.invoice_idInvoice the payment is applied to. NULL for an unapplied advance payment.
payment_datedatenoDate the payment was issued and recorded in the ledger.
amountnumeric(14,2)noAmount paid in SAR.
methodtextnobank_transfer, cheque, sadad (bill payment) or swift (international transfer).
bank_referencetextnouniqueReference that appears on the bank statement when the payment clears. Also stored as journal_entries.reference.
entry_idintegerno journal_entries.entry_idJournal 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.

ColumnTypeNull?KeyDescription
bank_account_idintegerno PKUnique number for each bank account.
account_nametextnoInternal name of the account.
bank_nametextnoBank holding the account (fictional).
iban_maskedtextnoMasked, synthetic IBAN.
gl_account_codetextno gl_accounts.account_codeLedger account that mirrors this bank account.
opening_balancenumeric(14,2)noStatement 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.

ColumnTypeNull?KeyDescription
statement_line_idintegerno PKUnique number for each statement line.
bank_account_idintegerno bank_accounts.bank_account_idBank account the line belongs to.
statement_datedatenoDate the bank booked the line.
value_datedatenoDate the money actually moved (value date).
descriptiontextnoBank's narrative, in capitals.
amountnumeric(14,2)noSigned amount in SAR: positive = deposit, negative = withdrawal.
bank_referencetextyesReference to match against vendor_payments.bank_reference or journal_entries.reference. NULL for bank charges.