Why Your Revenue Dashboard Is Wrong This Morning
The Slack message says "revenue looks off." You have 60 minutes before the standup. Here's the debugging order that works.
First: confirm it's actually wrong
Stakeholders are pattern-matchers. "Wrong" sometimes means "different from expected," which sometimes means "business is having a bad day." Two minutes here saves an hour later.
Ask:
- What specifically looks wrong? A specific number, a missing segment, a flat line?
- What were they comparing to? Yesterday? Last week? Their gut?
- Is the dashboard for today, or for a historical date that shouldn't change?
If the answer is "Q1 revenue is showing different than what we reported in earnings", that's actually wrong. If it's "today's revenue is lower than yesterday's", could just be a Wednesday.
Then check, in this order
1. Is the source data fresh?
Run the source-table freshness query for the table the dashboard ultimately reads from:
SELECT MAX(load_timestamp) AS latest, COUNT(*) AS rows
FROM source.orders
WHERE load_timestamp >= CURRENT_TIMESTAMP() - INTERVAL 24 HOUR;If latest is older than your SLA or rows is zero, the dashboard isn't wrong, the data didn't land. Now you're debugging the pipeline, not the math.
2. Did anything change in the last 24 hours?
Check three places:
- dbt run log: any models that failed, were skipped, or had row counts way off?
- Orchestrator log: any tasks that took unusually long or partial-loaded?
- Git log: any merges to the warehouse repo in the last 24 hours? Especially to the model that drives the dashboard.
The wrong-number incidents that take 4 hours to debug are almost always "someone shipped a transformation change yesterday afternoon." Check the obvious thing first.
3. Did the source schema change?
Compare today's column set against yesterday's for the source table:
SELECT column_name, data_type
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'orders'
ORDER BY column_name;Compare against your last known good schema (in git, in a snapshot, in your observability tool). Renames are the silent killer here, the column you're joining on still "exists," just under a different name, and your join silently produces NULLs.
4. Are the join keys still aligned?
The fact-dim join is where most revenue numbers go wrong. Two queries:
-- Fact rows with no matching dim
SELECT COUNT(*) AS orphan_rows
FROM fct_revenue f
LEFT JOIN dim_customer d ON f.customer_id = d.customer_id
WHERE d.customer_id IS NULL
AND f.revenue_date >= CURRENT_DATE() - 7;
-- Same query, last week, for baseline
SELECT COUNT(*) AS orphan_rows_baseline
FROM fct_revenue f
LEFT JOIN dim_customer d ON f.customer_id = d.customer_id
WHERE d.customer_id IS NULL
AND f.revenue_date BETWEEN CURRENT_DATE() - 14 AND CURRENT_DATE() - 8;If orphan_rows is more than 3x orphan_rows_baseline, you have a broken join. Likely culprits: dimension table didn't refresh, key column was renamed, or new customer records arrived that the dim hasn't caught up with.
5. Did a filter, join condition, or aggregation change?
This is where the "wrong math" lives. Pull up the SQL that powers the dashboard. Read it line by line. Specifically look for:
- WHERE clauses that filter out an entire region or segment (e.g.,
WHERE region != 'EU'added for a one-off and never removed) - JOINs that became INNER from LEFT, dropping rows
- SUM vs. COUNT vs. AVG getting swapped
- Timezone changes, UTC vs. local, that shift the "today" boundary
6. Is the BI tool caching stale results?
Last resort. Hit refresh / clear cache / re-run the query from the BI tool. If the number changes, the warehouse was fine and the BI tool was cached. Annoying but harmless.
The fastest path: prevent this from happening
The above sequence works at 8 AM on a Tuesday. It doesn't work at 6 AM, before stakeholders see the dashboard. The way to catch revenue-wrong before sales does:
- Monitor source freshness on a continuous schedule. Alert if the source table is older than your SLA.
- Monitor row-count anomalies on the fact table. Alert if today's count is > 3σ from baseline.
- Monitor NULL rate on join keys. Alert if today's rate exceeds historical p99.
- Monitor the metric itself. Alert if revenue moves more than ±25% day-over-day.
These four checks, running every hour, catch nearly every revenue-wrong incident before a human sees the dashboard. The same checks you'd build into a debugging runbook, just run preemptively.
How Sparvi handles this
Sparvi monitors handle the four checks above when you connect a warehouse: a freshness monitor on max(updated_at), row-count monitors with statistical evaluation on every profiled table, null-rate monitors on every column. Add a custom SQL monitor on the metric itself (revenue, signups, whatever your dashboards track), and segment it by region, channel, or product so a 60% drop in one slice does not get averaged away by healthy ones.
When something drifts, you get a Slack message at 6 AM, not a CEO message at 11. That's the entire pitch.
Catch the next one before sales does
14-day free trial, no credit card. Connect Snowflake or BigQuery, get monitors (freshness, row count, business metric, segmented when it matters) running in 30 minutes.
Start Free Trial