If you’ve ever tried to do “quick reporting” from a pile of exported spreadsheets, you already know the villain of this story: the Monthly_Final_v7_REALLYFINAL.xlsx file. It starts innocent. Then someone edits a column header. Another person deletes a tab. A third person copy-pastes “N/A” into a numeric field and swears it was fine on their screen. Suddenly, your dashboard is confidently reporting revenue that could fund a small country.
This post is about taking that chaos and turning it into something finance teams actually trust: a reliable process that moves data from raw exports to clean, consistent tables that can power a dashboard without surprises. The key is building in checks along the way so the numbers can be explained, traced, and defended.
What “Finance-Grade” Means IRL
In finance, correctness is not a nice-to-have. It’s the whole point. When I say finance-grade, I mean a few simple things:
If you re-run it, you get the same result.
You can trace every number back to the source rows.
Totals reconcile the way finance expects them to.
The process catches problems early instead of letting them quietly slip into reporting.
Definitions stay consistent from month to month.
In other words, it’s what happens when you treat reporting like a system, not a one-off.
The Goal: Source Exports to Dashboard, With Proof
The flow I aim for is straightforward:
Source exports (GL, bank, AP/AR) → raw tables → cleaned tables → reporting tables → dashboard
Think of it like cooking. Raw is what you received. Clean is where you standardize and remove issues. Reporting tables are where everything is shaped into something that’s easy to use and hard to misread. The dashboard is the final plating.
Insert image: a simple pipeline diagram showing Source → Raw → Clean → Reporting → Dashboard
Step 1: Bring Data in Without “Fixing” It
Financial data usually arrives through exports: general ledger activity, journal entries, bank transactions, vendor lists, budgets, cost center mappings. The temptation is to fix problems the moment you see them, especially if you’re importing from spreadsheets.
I do the opposite. The first stop is a raw layer where the data is stored exactly as it came in, plus a little context that makes it trackable later. I like to include things like the file name, when it was loaded, and an identifier for the load batch.
That way, if someone asks where a number came from, you can answer without guesswork.
Step 2: Make it Consistent in the Clean Layer
This is where spreadsheet cleanup turns into repeatable cleanup.
A few problems show up constantly in finance exports:
Column names change. One export says Amount, another says Amt, another says Transaction Amount. Dates come in as text. Amounts include commas. Missing values show up as “N/A” or “—”.] Duplicates happen from re-exports, manual merges, or overlapping date pulls.
Cleaning is less about clever tricks and more about being strict: standardize the columns, convert types the same way every time, and define a clear rule for removing duplicates.
Step 3: Organize it the Way Finance Reads It
For reporting, I like a simple structure: one main table that stores the transactions, and a handful of supporting tables that store the “lookups,” like account names or departments.
The main transactions table holds what happened and how much. The supporting tables hold the labels and groupings people want to slice by.
This setup makes reporting smoother because it prevents you from repeating the same text values a million times, and it keeps account and department definitions consistent.
Insert image: a simple table layout showing a transaction table connected to account, date, department, and vendor tables
Step 4: Add Checks That Catch the Things Finance Cares About
This is the part that makes the whole process trustworthy. You can have clean-looking data and still be wrong. Checks help you catch issues before anyone builds a story around them.
Here are checks I always like to include:
Balancing checks
If the data includes journal entries, debits and credits should match. If they don’t, something is missing or duplicated.
Reconciliation checks
Totals should match a known reference point from the source system. This can be row counts, total amounts for a date range, or totals by period.
Duplicate checks
Even one duplicated file can double your spend and make the dashboard look terrifying.
Reference checks
Every department code and account number in transactions should exist in the supporting tables. If a code is unknown, it should be flagged.
Unusual activity checks
If spending spikes sharply or revenue drops suddenly, it may be real, but it should still be surfaced for review.
SQL Examples
One of my favorite ways to handle duplicates is to create a stable “fingerprint” for each row using the fields that define uniqueness.
Example: build a stable key from business fields
SELECT
MD5(CONCAT(
COALESCE(CAST(posting_date AS STRING), ''),
'|', COALESCE(account_number, ''),
'|', COALESCE(department_code, ''),
'|', COALESCE(CAST(amount AS STRING), ''),
'|', COALESCE(description, '')
)) AS transaction_key,
*
FROM staging_gl;
Example: find duplicate fingerprints
SELECT transaction_key, COUNT(*) AS cnt
FROM clean_gl
GROUP BY transaction_key
HAVING COUNT(*) > 1;
Example: check balancing by batch
SELECT
batch_id,
ROUND(SUM(CASE WHEN dc_flag = 'D' THEN amount ELSE 0 END), 2) AS total_debits,
ROUND(SUM(CASE WHEN dc_flag = 'C' THEN amount ELSE 0 END), 2) AS total_credits,
ROUND(
SUM(CASE WHEN dc_flag = 'D' THEN amount ELSE 0 END) -
SUM(CASE WHEN dc_flag = 'C' THEN amount ELSE 0 END), 2
) AS diff
FROM clean_journal_entries
GROUP BY batch_id
HAVING ABS(
SUM(CASE WHEN dc_flag = 'D' THEN amount ELSE 0 END) -
SUM(CASE WHEN dc_flag = 'C' THEN amount ELSE 0 END)
) > 0.01;
If that last query returns anything, that’s a signal to stop and investigate. Quietly pushing it through would be the data equivalent of ignoring a check-engine light.
Step 5: Make Updates Small and Predictable
Reloading everything from scratch is easy to build, but it’s not always practical. Finance data changes in patterns: monthly close entries, reclasses, late invoices, adjustments.
A good approach is to load data in batches and update only the periods that changed. That keeps the process faster and reduces the chance of accidentally shifting old results.
Insert image: a simple diagram showing new batch → checks → update reporting tables
Step 6: Keep a Simple Log So You Can Explain Your Numbers Later
A dashboard is useful. Proof is better.
I like to keep a small run log table that tracks what was loaded and what happened to it. For each batch, store the file name, when it loaded, how many rows came in, how many made it through cleaning, what the key totals were, and whether any checks failed.
When someone asks, “Why does Marketing spend look higher this month?” you can answer with clarity: which batch changed, what period it affected, and whether anything was flagged.
What I’d Show On The Dashboard
To make this feel real (and also to make it portfolio-ready), I’d include both business views and a quick quality view.
Business views
P&L overview with clear definitions
Department spend by month
Top vendors and categories
Budget versus actual
Quality view
Last load time
Rows processed
Reconciliation difference
Any failed checks or flagged items
Insert image: a “data quality” section with tiles for batch status, totals, and last refresh.
Closing Thoughts
The point of this isn’t just building a report. It’s building a process that people can trust when decisions are on the line.
Finance teams don’t need perfect buzzwords. They need consistent definitions, totals that reconcile, and the ability to trace a number back to where it came from. Employers love it too, because it shows you can take messy inputs, build a repeatable system, and deliver reporting that stays stable over time.




Leave a comment