Cause 1: Caching and refresh lag

Most BI tools don't query the live database on every dashboard view — they run on a schedule (hourly, nightly) or cache results until manually refreshed. A dashboard showing yesterday's total while today's orders have already landed in the database is expected behavior, not a bug, if the refresh hasn't run yet.

check-latest-data.sql
-- Confirm how recent the underlying data actually is
SELECT MAX(updated_at) AS latest_row
FROM orders;
-- Compare this against the dashboard's stated last-refresh time
Advertisement

Cause 2: Timezone cutoff mismatches

timezone-bucket-mismatch.sql
-- If order_date is stored in UTC but the dashboard buckets in a local timezone,
-- rows near midnight shift between days between the two totals
SELECT
  order_id,
  order_date AS utc_timestamp,
  order_date AT TIME ZONE 'America/New_York' AS local_timestamp
FROM orders
WHERE order_date BETWEEN '2026-08-21 22:00:00' AND '2026-08-22 02:00:00';
-- Rows here can land on Aug 21 in one timezone and Aug 22 in the other

Cause 3: Hidden BI tool filters

Row-level security, permission-based filters, and dashboard-level defaults configured inside a BI tool often don't show up as a visible filter chip on the dashboard itself. Two people viewing the same dashboard can legitimately see different totals if one has a region restricted by a permission filter the other doesn't. The SQL-side check is to reproduce the dashboard's stated filters explicitly and compare — if a manual query with the same visible filters still doesn't match, an invisible filter is the likely explanation, and the BI tool's data model or permissions configuration needs checking directly.

Advertisement

Cause 4: Aggregation grain mismatches

grain-mismatch.sql
-- Summing at the order level
SELECT SUM(total_amount) FROM orders;

-- Summing at the line-item level — will differ if line items include
-- separately-tracked amounts like shipping or tax not in total_amount,
-- or if total_amount itself is a pre-computed, possibly stale, rollup
SELECT SUM(unit_price * quantity) FROM order_line_items;

Both queries are "correctly" summing revenue — they're just summing different things. Matching the dashboard's actual grain, not just its label, is the fix.

Real-world example: a revenue dashboard $1,800 short

A dashboard shows $52,300 in revenue for the current day; a direct query against the orders table returns $54,100. Checking the dashboard's refresh metadata shows it last ran 3 hours ago. Querying orders for rows created in the last 3 hours and summing their amounts returns exactly $1,800 — the dashboard isn't wrong, it simply hasn't ingested the last 3 hours of orders yet. The fix here isn't a data correction; it's either shortening the refresh interval or clearly labeling the dashboard with its actual "as of" time so viewers don't mistake a stale snapshot for a live number.

Common mistakes

  • Assuming the database is always "correct" and the dashboard is wrong, without checking which one is actually stale or scoped differently.
  • Not checking the dashboard's actual filter and permission configuration before concluding the underlying data is broken.
  • Comparing totals without confirming both are summing at the same grain.
  • Ignoring timezone settings on both the BI tool and the database connection when debugging a near-midnight discrepancy.

Key takeaways

  • A dashboard-vs-database mismatch isn't automatically a data bug — check refresh timing before assuming corruption.
  • Timezone bucketing differences show up specifically as errors clustered near midnight boundaries.
  • Hidden BI filters can make the same dashboard show different numbers to different viewers.
  • Always confirm both sides are aggregating at the same grain before comparing totals.