Skip to content
Preptima
hardScenarioDesignMidSeniorStaff

Two dashboards report different revenue for the same month. How do you find out why, and how do you stop it happening again?

Reconcile the two numbers row by row before touching the pipelines: the cause is almost always a grain mismatch that fans out a join, a filter difference over refunds or test accounts, or two timestamps meaning different things. The fix is one certified definition that both dashboards are forced to call.

5 min readUpdated 2026-07-29Target archetype: Big Tech, Enterprise Captive, Product Startup
Practice answering out loud

What the interviewer is scoring

  • Does the candidate reconcile at row level before forming a theory about which pipeline is wrong
  • Whether grain and join fan-out are checked early rather than blamed on source data
  • That filter and timestamp semantics are treated as part of the metric rather than as incidental SQL
  • Whether the proposed fix removes the ability to define the metric twice, not just the current discrepancy
  • Does the answer say which number is declared correct and who has the authority to declare it

Answer

Reconcile before you theorise

The instinct is to open both pipelines and read SQL until something looks wrong. That is the slow path, because both queries usually look reasonable and the difference is in what they mean rather than in how they are written. The fast path is to make the two numbers explain themselves: pull the underlying rows behind each figure at the finest grain both can produce, full-outer-join them on the natural key, and look at what does not match.

That reconciliation immediately classifies the problem. If one side has rows the other does not, it is a filter or a join difference. If both sides have the same rows with different values, it is a definition or a currency or a units difference. If one side has the same row several times, it is fan-out. And if the row sets differ only near the month boundary, it is time. Four classes, distinguished in one query, and each has a different owner and a different fix.

Fan-out: the join that invents revenue

The mechanism worth being able to draw from memory is a join between two facts at different grains. Suppose orders have a total, and shipments record one row per parcel. Joining them on order id and summing the order total multiplies each order's revenue by its parcel count, so a report is wrong in proportion to how many orders shipped in more than one box. The query is valid, the join key is correct, and the number is nonsense.

-- Wrong: one order with three shipments contributes its total three times.
select sum(o.order_total)
from   fact_orders o
join   fact_shipments s on s.order_id = o.order_id;

-- Right: aggregate the finer fact to the coarser grain first, then join.
select sum(o.order_total)
from   fact_orders o
where  exists (select 1 from fact_shipments s where s.order_id = o.order_id);

The generalisation is that an additive measure may only be summed at the grain it was declared at. Two facts join safely through their shared dimensions, not directly to each other, and where you genuinely need both you aggregate one to the other's grain first or you accept a semi-additive measure and stop summing it. A many-to-many bridge table has the same hazard with a friendlier name: a customer in three segments appears three times, and any measure summed across that bridge is inflated unless the bridge carries an allocation weight that sums to one.

Definition drift is the commonest cause of all

Assuming both queries are grain-correct, the difference is usually that the two dashboards are measuring different things while calling them the same word. Revenue is a particularly rich example. Gross or net of refunds. Net of discounts or not. Including or excluding tax, shipping, and gift-card redemptions. Booked at order time or at fulfilment or at cash receipt. Including internal test accounts, cancelled orders, and the staff discount programme, or excluding them. In a company with more than one currency, converted at the transaction-date rate, the month-end rate, or the current rate.

None of those choices is wrong. What is wrong is that two teams each made a defensible choice, wrote it into a WHERE clause, and shipped a tile labelled "Revenue". No amount of data quality work fixes that, because the data is fine and the disagreement is semantic. This is why the first question to ask the two dashboard owners is not "which pipeline is broken" but "what does your number include", and why the answer to the discrepancy is frequently that both numbers are correct and one of them is mislabelled.

Time, which is three different columns

The second recurring cause is that a month is not a well-defined set of rows until you say which timestamp defines membership and in which zone you evaluate it. An orders fact typically carries at least an event time, an ingestion time and a last-updated time, and grouping by each produces a different March. Add a warehouse that stores timestamps in UTC while the business reports in local time, and roughly the last hour of every month moves.

Late arrival compounds it. If one dashboard is a materialised table built on the second of the month and the other queries the base fact live, then every record that arrived late is in one and not the other, and the gap grows for a few days and then stabilises. That is not a bug in either, but it does mean a certified monthly number needs a stated close policy: after which point the month is frozen, and whether corrections after that appear as restatements of the original period or as adjustments in the current one.

Making a second definition impossible rather than merely discouraged

Explaining the discrepancy is the easy half. The design question is why the metric could be written twice at all, and the answer is that both dashboards were allowed to reach the base fact tables and write their own aggregation. Every fix that survives works by removing that option.

The practical shape is a governed layer between the model and the tools: a metric defined once, in version control, with its measure, its grain, its filters, its time column and its permitted dimensions declared, and both dashboards forced to reference it by name rather than to re-derive it. Whether that is a semantic layer in the BI tool, a metrics definition in your transformation framework, or a small set of certified views is much less important than the property being bought, which is that there is exactly one place the definition can change and one place to look when someone asks what the number includes.

Around it put the cheap guards. Tests that assert grain uniqueness on every fact's declared key, so fan-out is caught by a failing build rather than by a wrong dashboard. A convention that distinguishes certified from exploratory content in the tool itself, so a reader knows which number carries a promise. And a reconciliation job that recomputes the headline figure against the source system on a schedule, because a definition that is right and a pipeline that has silently stopped loading produce the same confident tile.

The discrepancy is nearly always semantic rather than technical, and the durable fix is not correcting one query. It is making it structurally impossible for the metric to be defined in two places, and stating who owns the definition when the two teams disagree about what revenue means.

Likely follow-ups

  • The difference is exactly the value of refunds. Which dashboard is right, and who decides?
  • How would you count distinct customers when your only fact is at order-line grain?
  • A dimension attribute is corrected retrospectively and last quarter's certified number moves. Is that a defect?
  • Where does a semantic layer stop helping, and what does it cost you to adopt one?

Related questions

dimensional-modellinggrainmetric-definitionssemantic-layerdata-quality