On this page
The short answer
The board pack says one number. The CRM says another. Finance’s spreadsheet says a third. Every one of those figures has an honest explanation, and nobody wants another meeting about which one is right.
Here is the short answer. Finance and Sales disagree because each source measures revenue differently. They count at a different grain (the level of detail that one row represents), over a different time window, using a different definition of the word, and with a different idea of who the customer is. None of the systems is broken. They are answering slightly different questions with the same word.
That is why rebuilding the dashboard does not help. The chart is a faithful renderer: it shows whatever the query returns. The disagreement lives upstream, in the sources and the definitions, not in the visualisation. So the fix is upstream too.
What this usually looks like
It usually starts in a monthly meeting. The revenue on the board pack does not match the number the sales director quotes from the CRM. Someone opens Finance’s workbook and finds a third figure. Each person is certain their number is correct, because within their own system it is.
The commercial team counts a deal when it is signed. Finance counts revenue when it can be recognised, spread across the months in which the service is actually delivered. Billing counts money when it raises an invoice. A refund is a negative order in one system and a deleted order in another. A single customer is one account in the CRM, two billing entities, and three logins in the product.
So the meeting turns into archaeology. People screenshot tiles, export spreadsheets, and argue from totals. An hour later everyone agrees to “look into it”, and the same gap reappears next month. The pattern is familiar: the numbers are close enough to feel like they should match, and far enough apart to matter.
Before you rebuild anything
You can usually locate the cause yourself in an afternoon, without touching the BI tool. The goal is not to force the numbers to match. It is to explain the gap: to break it into named, understood parts. A reconciliation is finished when every pound of difference has a reason, not when the difference happens to be small.
Work from records, not totals. Two totals can match by accident, built from errors that cancel out. Two totals can differ for a reason that is entirely correct. Only the underlying rows tell you which.
Those seven classes are the whole vocabulary of a reconciliation. Name the class and you have named the cause.
What the result tells you
Each class points at a different cause, and each cause has a different owner. Read your gap like this.
- Timing: the sources were measured at different moments, or over slightly different windows. Likely cause: different refresh schedules, or one number includes late-arriving transactions the other has not seen yet.
- Identity: the same real-world customer appears as more than one record, or two different customers share an identifier. Likely cause: no shared customer key across systems.
- Scope: the sources include different populations. Likely cause: one figure excludes a region, a legal entity, intercompany trade (sales between parts of the same group), or test accounts that the other keeps.
- Duplication: a single transaction is counted more than once. Likely cause: a re-import, or a join that fans out (see grain and cardinality below).
- Currency: figures are held in different currencies, or converted on different dates at different rates.
- Grain: the sources count different units, such as orders versus order lines. Likely cause: summing a table without checking what one row represents.
- Definition: the systems mean different things by the word. Likely cause: Finance recognises revenue over time; Sales books it at signature.
The last one matters most. A definition difference is not something engineering can fix on its own. Someone has to decide which definition is canonical for the board pack, and then everyone has to use it.
What is happening underneath
Underneath the reconciliation, four technical ideas explain almost every gap.
Grain is the level of detail that one row represents. An orders table has one row per order; an order-lines table has one row per line within an order. Sum the wrong one and you double-count. Every table should have one clearly stated grain, and any join that crosses grains should fan out on purpose, not by accident.
Source freshness is how recently a source was last updated. If one dataset refreshes hourly and another nightly, the two will disagree every morning, and both will be correct. Always check the refresh timestamp before you suspect the logic.
Entity identity is deciding when two records describe the same real-world customer. Source systems mint their own keys, so one company can be three account IDs in the CRM, two in billing, and one in the warehouse. Identity resolution (matching those records to a single canonical customer) is the deepest and most common cause of disagreement, and it belongs upstream of anything a stakeholder sees. It is the same problem as building one reliable customer view, and it rarely solves itself.
Join cardinality is how many rows on one side of a join match rows on the other. A one-to-many join (one customer to many invoices) will multiply a figure the moment you sum across it. Duplicated revenue almost always traces back to a join that fanned out, or a source imported twice.
Two short queries take most of the guesswork out. The first sums each source over an identical window, so any remaining gap cannot be blamed on the date range. It uses a half-open range (on or after the first of June, and strictly before the first of July), which counts every day of the month exactly once and never spills into the next.
-- 1. Sum each source over exactly the same window select 'finance' as source, sum(amount_gbp) as revenue from finance.recognised_revenue where recognised_date >= date '2026-06-01' and recognised_date < date '2026-07-01' union all select 'billing' as source, sum(amount_gbp) as revenue from billing.invoice_lines where invoice_date >= date '2026-06-01' and invoice_date < date '2026-07-01';
If the totals still differ, the second query looks for duplication. It groups the billing lines by invoice and keeps only the invoices that appear more than once. Any row it returns is a duplicate inflating the total: the signature of a fan-out join or a double import.
-- 2. Find duplicated invoices inflating the billing total select invoice_id, count(*) as line_rows from billing.invoice_lines where invoice_date >= date '2026-06-01' and invoice_date < date '2026-07-01' group by invoice_id having count(*) > 1;
Between them, these two checks separate a timing gap from a grain or duplication gap in minutes. What they cannot resolve is a definition difference, because that is a decision about the business. That decision needs a home, and the home is a semantic layer: one governed place where a metric such as revenue or active customer is defined once and reused by every report. Without it, each analyst re-implements the metric in their own query, and the definitions drift apart within weeks.
What good looks like
Good does not mean every system holds the same number. It means every number is explainable and traceable. In practice that looks like four things.
- One governed definition per metric, written in plain English, owned by a named person, and used by every report — the semantic layer, not forty copies scattered across forty queries.
- Sources reconciled on a routine, especially finance against the warehouse, so the gap is a monitored check rather than a monthly fire drill.
- Automated tests that fail loudly when a source double-counts, a join fans out, or a total moves more than expected.
- Lineage you can follow: the ability to trace any figure on a dashboard back through the model to the source row it came from.
When these are in place, a disagreement becomes information. It tells you two definitions have diverged, and you can say exactly where and why — in minutes, not in another meeting.
Common ways this goes wrong
- Rebuilding the dashboard. It moves the argument to a new chart; it does not settle it. The cause is upstream.
- Comparing totals instead of records. Matching totals can hide compensating errors, and differing totals can be entirely correct. Only the rows tell the truth.
- Ignoring timing. Two numbers pulled at different moments will differ, and no amount of logic-checking will explain a gap that is really about refresh schedules.
- Letting joins fan out. A one-to-many join silently multiplies revenue. If a total jumps after a “small” model change, suspect cardinality first.
- “Correcting” the number that looks wrong until the totals match, without understanding why. This buries the problem instead of fixing it, and it returns next month.
- Defining metrics inside individual reports. The definition drifts the moment a second analyst writes a second query.
When DIY stops being sensible
The afternoon reconciliation is worth doing, and often it is enough. It stops being enough when the same reconciliation has to run reliably every day, across systems that each define a customer differently, with nobody clearly responsible for the result. At that point you are no longer explaining a one-off gap. You are running a system (at every monthly close, when the accounts are finalised), and it needs to be built like one.
A decision guide
Once you have classified the gap, the first move is usually obvious. Use this as a quick lookup.
| Difference class | What it means | First fix |
|---|---|---|
| Timing | Sources measured at different moments or windows | Compare refresh timestamps; re-pull both for an identical window |
| Grain | Sources count different units (orders versus lines) | State each table’s grain; sum at the right level |
| Identity | One customer as many records, or a shared ID | Resolve to one canonical customer key upstream |
| Duplication | A transaction counted more than once | Run the group-by / having check; find the fan-out join or re-import |
| Scope | Different populations included | Agree the filters: region, entity, intercompany, test accounts |
| Currency | Different currencies or conversion dates | Fix one reporting currency and one conversion rule |
| Definition | Systems mean different things by the word | Agree the canonical definition, then encode it once |
If you are not sure which class is causing most of your gap, a Data Diagnostic is a fast way to find out — and to tell whether this is a one-afternoon reconciliation or a system that now needs building.