Sample dataset · CC0

A statement whose balance actually reconciles.

Eight columns of transaction across twelve accounts and eighteen months. Amounts are signed, money in is positive, money out is negative, and each account's balance column is the previous balance plus the amount on that row, every time.

8 columns · 100 or 1,000 rows as a file · up to 1,000,000 rows generated here · CSV, JSON, JSONL, Excel, Parquet

Take a file

Reconciliation only works within an account. Filter to one account_id first, or sort by account and date, before you check the balance column against the amounts.

More history per account

Larger sizes keep the same twelve accounts, so a hundred thousand transactions is about 8,300 per account, which is enough to make a window function do real work.

The first eight rows

The first eight rows of the 1,000-row file. Each row is a different account, which is why the balances look unrelated.

transaction_idposted_dateaccount_iddescriptioncategoryamountbalancecurrency
TXN-0000012025-04-01ACC-0001STARBUCKS #1042Coffee-94.755055.25USD
TXN-0000022025-04-02ACC-0002SPOTIFY P07C1BSubscriptions-766.152898.85USD
TXN-0000032025-04-02ACC-0003AMZN MKTPLACEShopping-462.254909.75USD
TXN-0000042025-04-03ACC-0004STARBUCKS #1042Coffee-197.3310563.67USD
TXN-0000052025-04-03ACC-0005APPLE.COM/BILLSoftware-359.179836.83USD
TXN-0000062025-04-04ACC-0006TRANSFER TO SAVINGSTransfer-270.026894.98USD
TXN-0000072025-04-04ACC-0007AMZN MKTPLACEShopping-61.1010406.90USD
TXN-0000082025-04-05ACC-0008PAYROLL DEPOSITIncome2780.7612212.76USD

Eight columns, and what each one holds

Column Type What it holds Example
transaction_id text TXN-000001 upward. TXN-000001
posted_date date Date the transaction cleared, ascending through the file. 2025-04-01
account_id text ACC-0001 to ACC-0012. Twelve accounts share the file. ACC-0001
description text Merchant or transfer text, in the shouty format banks actually emit. STARBUCKS #1042
category text Spending category. Coffee
amount decimal Signed amount: negative for money out, positive for money in. -94.75
balance decimal Running balance for that account after this row. Adds up exactly, so a window function can be checked against it. 5055.25
currency text Always USD in this file, kept as a column so currency handling has something to read. USD

What it models

A bank statement export covering twelve accounts at once, which is the awkward shape a personal finance tool or an accounting import actually receives. One row is one cleared transaction, with a signed amount and the balance that resulted from it.

Descriptions are the uppercase merchant text banks emit, including the trailing store numbers and reference codes that make the field almost but not quite categorizable. That is a real problem, and the category column beside it is what a categorization step would have produced, which means you can use this file to score your own categorizer against a known answer.

The running balance

This is the only dataset in the collection where a row depends on the row before it. For each account, the balance is the previous balance for that account plus the amount on the current row, rounded to cents once. Every account starts from its own opening balance and moves from there.

It is also the reason this dataset is useful. A running total is one of the first things people try to compute with a window function, and almost every sample dataset makes it unverifiable by generating the balance independently. Here you can compute SUM(amount) OVER (PARTITION BY account_id ORDER BY posted_date, transaction_id) plus the opening balance, and it should match the balance column exactly. A test in the repository does that across 600 rows.

The catch, and it is a deliberate one, is that transactions from the twelve accounts are interleaved in the file. Compute the running total without partitioning by account and you get a number that is confidently wrong on every row. That failure is the exercise.

Signs and categories

Money out is negative and money in is positive, which is one of two conventions banks use and the one that makes arithmetic easier. Income rows are payroll deposits between 1,800 and 6,000; spending rows are squared draws that put most transactions under a hundred and a few in the thousands.

Fourteen description and category pairs cover the usual ground: groceries, fuel, subscriptions, transport, housing, utilities and the rest. Because the pairing is fixed, category is a clean derived label, and a spend-by-category chart from this file looks like a personal finance app's home screen.

What people use it for

  • Window functions, specifically a running total partitioned by account.
  • Demonstrating what happens when you forget the partition clause.
  • Personal finance dashboards: spend by category, income against outgoings by month.
  • Testing a transaction categorizer against a known-correct category column.
  • Signed-amount handling, where a naive SUM of absolute values gives the wrong total.

License, and the people in it

This dataset is dedicated to the public domain under CC0 1.0. Put it in a course, a paid product, a test suite, a bug report, a screenshot, a conference talk or a book. There is nothing to ask for, nothing to sign and no attribution required. A link back is welcome and is not a condition.

No real account, card, person or merchant appears in this file. Account identifiers are sequential strings, the descriptions are the shouty generic text banks emit, and no number here corresponds to anything that exists.

Questions people ask about this file

Does the balance column really reconcile?

Within each account, on every row. The balance is the previous balance for that account plus the current amount, rounded once to cents, and a test walks 600 rows and checks it. Across accounts it does not reconcile and should not, because the twelve accounts are interleaved in one file and each has its own opening balance.

Why are the accounts interleaved rather than grouped?

Because that is how a multi-account export arrives, and because the mistake it invites is the point. Computing a running total without partitioning by account gives a plausible-looking column of wrong numbers on every row. Having a file where the correct answer is already sitting in the next column makes that mistake visible in seconds instead of in a quarterly report.

Are the amounts signed?

Yes. Money out is negative and money in is positive, which is the convention that makes a running balance a simple addition. It also means that summing the absolute values gives you total money moved rather than net change, and confusing those two is a common bug in personal finance imports.

Can I use it to test a transaction categorizer?

That is one of the better uses for it. The description column carries the uppercase merchant text with store numbers and reference codes attached, which is genuinely hard to parse, and the category column beside it is the correct answer. Run your categorizer on the descriptions and score it against the categories.