Skip to content

Data Quality Checks to Run Before Publishing a Dashboard

A dashboard can load cleanly and still show the wrong number. Six data quality checks, each with SQL and pass/fail rules, to run before anyone uses the numbers.

  • Published On:
  • 12 min read
Vishnupriya BData Analyst specializing in data visualization, SQL, Python, and data modeling.

Summarize with

Data Quality Checks to Run Before Publishing a Dashboard, Databrain blog cover

Key Takeaways

  • A dashboard that loads isn't always right. In the worked example, a join repeats one order and turns $250 of paid orders into $350, with no error anywhere.
  • Write the rules before you test anything: reporting window, grain, which rows count, required fields and how close the totals must match.
  • Six checks catch most errors before release: freshness, required fields, duplicate keys, join fan-out, source reconciliation and filters.
  • Compare against a separate source query. Running the same model twice just repeats its mistakes.
  • Save the evidence. If there's no recorded result, snapshot ID or owner, the next reviewer can't repeat the check.

A sales dashboard shows $350 in paid orders. The source records add up to $250. Both queries run, every chart loads, and nothing throws an error. So which number goes in front of the sales team? The $100 gap comes from one order total being counted twice when orders are joined to order items. That's the kind of error data quality checks are there to catch.

Data quality checks for dashboards are tests you run on a dashboard's data, model and filters before anyone uses the numbers. They check that the data is complete and up to date, and that each record shows up once. They also check that every total on the dashboard matches a separate query on the source, for the same snapshot and filters.

This guide walks one small sales dashboard example through six checks: freshness, missing values, duplicate keys, join fan-out, reconciliation and filters. Each one comes with SQL and a pass/fail rule. The numbers are made up for the example, so use your own source results and dashboard screenshots for a real release.

If your data isn't consolidated and modeled yet, start with our data readiness foundation. This article has a narrower job: deciding whether one specific dashboard is ready to publish.

What are data quality checks for dashboards?

Data quality checks for dashboards confirm six things before release. The reporting period is complete. Required fields are filled in and stored as the right type. Each key appears once at the intended grain, and joins don't multiply the measure. Totals match a separate source query, and every filter returns the expected result. Each check needs a written rule, a recorded result and an owner.

Most data quality testing stops at the table. A dashboard adds three more places for errors to hide: the joins behind a metric, the way it aggregates, and the filters a viewer clicks. A table can pass every test while the chart built on it is still wrong. That's why dashboard testing needs its own checks.

Check

What it catches

Typical test

1. Freshness and coverage

A late load, or a load missing a file or partition

Load time plus source-to-load record counts for the window

2. Types and required fields

NULL amounts, blank statuses, failed parsing

Count of rows failing any required-field rule

3. Duplicate keys

The same order loaded twice

GROUP BY key HAVING COUNT(*) > 1

4. Join fan-out

An order-level amount summed at item-level grain

Rows per key after the join

5. Reconciliation

A dashboard total that differs from the source

Separate source query, compared before rounding

6. Filters

A card that ignores a filter or keeps the wrong scope

One expected result per filter state

Which data quality dimensions do these checks cover?

The UK Government Data Quality Framework uses six core data quality dimensions defined by DAMA UK: completeness, uniqueness, consistency, timeliness, validity and accuracy (UK Government Data Quality Framework). The framework says the list isn't fixed, and teams can adapt it. Here's how the six dashboard checks line up with it:

Dimension

What it measures

Dashboard check

Timeliness

Data reflects the period it represents and is up to date

Check 1: freshness

Completeness

All expected records, and their most important fields, are present

Check 1: coverage; Check 2: required fields

Validity

Values are in the expected range and format

Check 2: types and accepted values

Uniqueness

Each entity is recorded once

Check 3: duplicate keys; Check 4: join fan-out

Consistency

Values don't contradict other values for the same entity

Check 5: reconciliation; Check 6: filters

Accuracy

Data matches reality

Not proven by any check here

Look at the last row. Reconciliation proves the dashboard matches the source. It can't prove the source matches what really happened. If someone typed a wrong value upstream, it will reconcile perfectly. Accuracy needs its own controls where the data is entered, owned by whoever owns the source system.

Define acceptance criteria before you run any checks

Start with the decision the dashboard supports. A daily sales review needs a complete reporting day, a clear definition of an order, and totals that agree with the source at the same point in time. Without written rules, "the numbers look about right" becomes the test.

In our example, the KPI is paid order value: the sum of order amounts for paid orders, not counting tax and shipping. It measures sales activity, not recognized revenue. Refunds would need their own agreed treatment.

Write the rules down before you run anything:

Acceptance criterion

Rule for this example

Reporting period

November 8, 2026, from 00:00 UTC up to, but excluding, November 9 at 00:00 UTC

Data cutoff

A complete source extract through that period, loaded by 08:00 UTC on November 9

Grain

One row per order; order_id uniquely identifies an order

Population and units

status = 'paid'; USD amounts stored as decimal dollars

Required fields

Order ID, customer ID, order timestamp, status, currency and order total

Reconciliation

Exact order-count and decimal-total agreement for the same snapshot and filters

Filter behavior

Every intended card responds correctly; documented exceptions keep their scope

These limits belong to this example. Agree yours with the metric owner, including how to handle rounding and late records. A gap nobody can explain shouldn't pass just because it's small.

Be clear about who does what. Extraction should keep source IDs and track rejected records. Pre-processing should standardize types, units and categories. Data engineering should make loads and transformations repeatable. Whoever builds the dashboard has to keep the metric definitions from the model intact through aggregations and filters.

Record the source snapshot or batch ID, the model version, the dashboard version and the QA timestamp. If you compare a dashboard from one load with a source query from a later load, you can't tell which result is right.

Check 1: Is the data fresh and complete?

A recent refresh time only proves that something refreshed. Data freshness and completeness are two separate questions, and a dashboard can pass one and fail the other. So record three times separately: the period the source covers, when ingestion finished, and when the dashboard queried or refreshed.

Was everything that should have arrived actually loaded? Check that every expected source, date partition or file came in. Compare source and loaded record counts, or key sets, for the reporting window. One new row doesn't prove the whole load is there.

In our example:

  • A load that finished at 07:45 UTC passes the timing rule only if it also holds the complete November 8 extract.
  • A load that finished at 08:20 UTC misses the deadline.
  • A 07:45 load missing one expected file fails completeness, even though it's on time.

Keep the load record and the source-to-load comparison as evidence. Some sources can have quiet days with no activity. If that's possible, tell a confirmed empty day apart from a feed that never arrived. Zero orders and no data aren't the same result.

Check 2: Validate types and required fields

These checks confirm that every row the KPI depends on can be read and counted. Run them on the candidate data before any business filter. Otherwise a filter like status = 'paid' quietly drops a row with a missing status before any test sees it.

Check types before aggregating

Look at both the source schema and the model schema. Order amounts should be numeric. Timestamps should use a documented timezone. IDs should keep leading zeros that mean something.

During pre-processing, keep counts and samples of values that fail to parse. If a bad amount turns into NULL, or the row gets dropped, the transformation can finish cleanly while the KPI quietly changes.

Profile daily counts, amount ranges and category mixes to spot unexpected shifts. The NIST/SEMATECH e-Handbook of Statistical Methods describes exploratory data analysis as a way to uncover underlying structure, detect outliers and anomalies, and test underlying assumptions. An odd value is a reason to dig in, not proof of an error.

Test required fields

The SQL below uses PostgreSQL-compatible syntax. dashboard_orders is the candidate model you're checking. source_orders is the separate source snapshot. Timestamps are in UTC, and both tables come from the same validation run.

sql
SELECT
    COUNT(*) AS rows_checked,
    COALESCE(SUM(CASE WHEN
        order_id IS NULL
        OR customer_id IS NULL
        OR order_ts IS NULL
        OR status IS NULL OR TRIM(status) = ''
        OR currency IS NULL OR TRIM(currency) = ''
        OR order_total IS NULL
        THEN 1 ELSE 0 END), 0) AS invalid_rows
FROM dashboard_orders;

Pass: zero invalid rows, and the row count you expect from the source. Fail: any missing required value, an empty dataset you didn't expect, or a source record you can't account for.

Also check statuses and currencies against the agreed list. A field can be filled in and still hold a wrong value. Optional fields need their own rules. For example, a missing note matters far less than a missing order amount.

Don't swap unknown amounts for zero to make the check pass. Keep rejected records traceable, fix the source or the transformation, and rerun the checks it affects.

Check 3: Are there duplicate keys at the dashboard's grain?

A duplicate key check confirms that each record shows up exactly once at the grain the KPI assumes. So decide the grain first. An order table should have one row per order. An order-item table can rightly have several rows for one order. If you're not sure what grain a table has, our guide to data modeling explains how grain and keys work.

sql
SELECT order_id, COUNT(*) AS row_count
FROM dashboard_orders
GROUP BY order_id
HAVING COUNT(*) > 1;

Pass: the query returns no rows, and the required-field check also passes. Fail: it returns any repeated order key.

If IDs are only unique within one source or tenant, test the full composite key. For event history or snapshots, include the version or date that makes a record unique.

Don't just delete the repeats. Find out where they came from first: an extraction retry, real history, or a bad merge. Then apply a written rule for handling them.

Check 4: Does a join fan out your KPI?

Join fan-out happens when one row matches several rows in another table, so the joined result has more rows than you started with. It breaks a KPI when an order-level amount gets summed at the item-level grain the join creates. You won't see an error. The total is just too big.

Take these paid orders for November 8:

Order ID

Region

Order value

Matching item rows

1001

North

$100

2

1002

South

$100

1

1003

North

$50

1

The right answer is 3 orders and $250. Join the item table and sum order_total, and you get 4 rows and $350, because order 1001 adds its $100 twice.

Diagram of join fan-out: three order rows totalling $250 become four rows totalling $350 after a left join to order items, because order 1001 has two item rows
Join fan-out in the worked example: order 1001 matches two item rows, so its $100 total is counted twice.

This query finds the orders the join multiplies:

sql
SELECT
    o.order_id,
    COUNT(*) AS rows_after_join
FROM dashboard_orders AS o
LEFT JOIN order_items AS i
    ON o.order_id = i.order_id
WHERE o.status = 'paid'
  AND o.currency = 'USD'
  AND o.order_ts >= TIMESTAMP '2026-11-08 00:00:00'
  AND o.order_ts <  TIMESTAMP '2026-11-09 00:00:00'
GROUP BY o.order_id
HAVING COUNT(*) > 1;

For an order-level sum, look into every order this returns. Check for unmatched keys too. An inner join can drop orders even when it creates no duplicates. A left join keeps unmatched orders, but a later filter on the right-hand table can drop them again.

How do you fix join fan-out?

Calculate paid order value from the order-grain model. If you need item data, roll it up to one row per order before the join. Or build a separate item-level measure with a clear rule for splitting the amount.

Two common shortcuts don't work:

  • COUNT(DISTINCT order_id) only fixes the order count. The value is still too high.
  • SUM(DISTINCT order_total) isn't a fix either. In PostgreSQL, an aggregate with DISTINCT runs once for each distinct value of the expression (PostgreSQL documentation), not once per order. Orders 1001 and 1002 are both $100, so they collapse into one value. You get $150, which is wrong in the other direction.
Bar chart of paid order value calculated three ways: $250 at order grain (correct), $350 after joining order items ($100 too high), and $150 with SUM(DISTINCT) ($100 too low)
The same three orders give three different totals. Only the order-grain sum is correct.

Check 5: Does the dashboard reconcile with the source?

To validate KPI dashboard data, compare the dashboard with a source query that applies the agreed definition on its own. If the model is wrong, how would querying it again tell you? It wouldn't. The same mistake comes back. So compare against the source snapshot, at the same point in time, with the same filters.

sql
SELECT
    COUNT(*) AS paid_orders,
    COALESCE(SUM(order_total), 0) AS paid_order_value
FROM source_orders
WHERE status = 'paid'
  AND currency = 'USD'
  AND order_ts >= TIMESTAMP '2026-11-08 00:00:00'
  AND order_ts <  TIMESTAMP '2026-11-09 00:00:00';

You should get 3 orders and $250. The end date uses < the next day, which covers the whole reporting day no matter how precise the timestamps are.

A matching grand total isn't the end of it:

  1. Compare the exact number before display rounding. $249.996 and $250.00 look the same on a card.
  2. Reconcile by day and by region. A correct total can hide errors that cancel out.
  3. Compare the included order IDs both ways. One missing order and one extra order can still give a matching total.
  4. Check the parts of any ratio. Average order value is total qualifying order value divided by qualifying orders. If you average the regional averages without their order counts, you get a different answer.

The same data validation applies when an AI writes the query. If you're checking AI-generated metrics, see our guide to validating LLM-generated SQL in production.

Check 6: Do filters return the right numbers?

Filter testing checks that every card returns the expected result in each filter state, not just the default view. Treat each state as its own test case, with an expected count and value. A card that ignores a filter still shows a believable number. It's just the number for the wrong rows.

Here's the reconciliation checklist for the worked example. These are the results you should see in the dashboard:

Filter case

Expected order count

Expected value

November 8, all regions

3

$250

November 8, North

2

$150

November 8, South

1

$100

November 8, North and South

3

$250

Clear region selection

3

$250

A valid region with no qualifying orders

0

$0 under this KPI's agreed empty-state rule

Test the edges too:

  • The first and last timestamps in the window, plus a row exactly on the next day's boundary.
  • Saved default filters.
  • Dashboard-level and metric-level filters used together, including a combination that returns no records.

Capture the filter values in effect, the query, the raw result and the number shown, all together. Check the view people will actually get, plus any exports or scheduled reports in the release, with their real filter settings. If results are cached, note which data snapshot the cache holds, and check again after the planned refresh. Where filters also decide who sees which rows, test them for each role. Role-based filters are a governance control, not just a convenience.

How to run these checks in Databrain

The checks above work in any tool. If you build dashboards in Databrain, here's where each one fits.

Start by connecting the source you validated, using the data source connection guide. That way the dashboard reads the same snapshot your checks ran against. When you create the metric, check its dimensions, measure, aggregation, joins and filters against the KPI definition.

Run your source query in the Custom SQL console. Run Query shows the result in a table, and you can download it as a CSV. Keep that file with your source comparison, then check the saved chart against it. Our post on the Custom SQL revamp walks through the editor.

Test filter scope separately. A dashboard filter applies across the whole dashboard. A metric filter applies to one metric card. Check each card you care about against its expected SQL result.

Finally, make sure Custom SQL filter variables actually resolve. A filter set to "Variable for Custom SQL" only changes a metric whose SQL uses that variable. The filter troubleshooting guide lists three rules worth checking during QA:

  • The {{variable}} name must match the filter's variable name exactly. It's case-sensitive.
  • Give the filter a default value. If a {{variable}} is still unresolved when the query runs, you get a "Filter or Client variables detected" error.
  • A multi-select filter needs WHERE region IN {{global_region}}, without your own brackets. = {{var}} or IN ({{var}}) produces invalid SQL.

The sign-off described here is a team QA process. It doesn't rely on a built-in approval step.

Build a repeatable sign-off checklist

A dashboard data quality checklist only helps if it keeps the evidence. For each check, save the rule, the result, the pass/fail status, where the evidence lives, the owner and the run time. Store it with the dashboard and model versions, so a second analyst can repeat the check and get the same answer.

These failure cases from the worked example show what passing looks like:

Check

Failing evidence

Evidence required to pass

Freshness and coverage

Load finishes at 08:20, or an expected file is missing

Completion by 08:00 and complete source coverage

Required fields

One order has a missing amount

Zero required-field failures; excluded records accounted for

Unique keys

Order 1002 appears twice

Zero duplicate key groups

Join grain

Order 1001 becomes two rows

Order-level measure stays at one row per order

Reconciliation

$350 in the chart versus $250 in the source

Three orders and $250 for the agreed snapshot

Filters and delivery

A card stays at $250 after selecting North

Captured result of two orders and $150 on each intended card

For a real release, give every check one of four statuses: PASS, FAIL, NOT RUN or NOT APPLICABLE. A required check that hasn't run doesn't count as a pass. A check marked not applicable needs a reason, such as scheduled reports being outside this release.

Publish only when every blocking check passes and the metric owner has reviewed the evidence. If you let a non-blocking issue through, give it an owner, a note on its impact, an expiry date and a follow-up. After you fix something, keep the failed result and attach the rerun. Don't overwrite the history.

Automate the stable checks upstream

Automate the checks that don't change from release to release. These automated data quality checks belong in your pipeline. Out of the box, dbt data tests include four generic tests: unique, not_null, accepted_values and relationships. Singular tests are SQL queries that return failing records, which suits business rules like "no paid order without an amount."

Table tests can't see the final report, though. Keep the filter and delivery checks next to them, because the join, the aggregation and the filter state only exist at the dashboard layer.

When should you rerun dashboard data quality checks?

Rerun the checks a change affects whenever extraction, transformations, metric definitions, joins, filters or report settings change. You don't always need the full set. A new filter needs Check 6. A changed join needs Checks 4 and 5.

Start with the dashboard's most important KPIs and keep their evidence up to date. That gives the next reviewer solid ground for publishing the numbers, and gives everyone reading the dashboard a reason to trust them. If you're setting up governed metrics for your own team, see how Databrain handles AI business intelligence.

How this guide was checked: The Databrain steps were fact-checked against Databrain's product documentation in November 2026, and the outside definitions link to their primary sources. The SQL uses PostgreSQL-compatible syntax, and the worked example uses made-up figures. Spotted something out of date? Contact our team.

Vishnupriya B

Data Analyst specializing in data visualization, SQL, Python, and data modeling.

Frequently Asked Questions

What are data quality checks?

Data quality checks are tests that measure data against quality dimensions such as completeness, uniqueness, consistency, timeliness and validity, the dimensions DAMA UK defines alongside accuracy. For a dashboard, that means checking freshness, required fields, duplicate keys, join fan-out, source reconciliation and filters. Each check needs a written rule, a recorded result and an owner.

How do you test a dashboard before publishing it?

Write acceptance criteria first: reporting window, grain, population, required fields and reconciliation tolerance. Then run six checks: freshness and coverage, types and required fields, duplicate keys, join fan-out, source reconciliation and filter states. Publish only when every blocking check passes and the metric owner has reviewed the recorded evidence.

What is join fan-out in SQL?

Join fan-out happens when one row in a table matches several rows in another, so the joined result has more rows than the original. Sum a value stored once per parent row, such as an order total, and it's counted once per matching child row. The KPI is inflated without any error.

How do you find duplicate records in SQL?

Group by the key that should be unique and keep groups with more than one row: SELECT order_id, COUNT(*) FROM orders GROUP BY order_id HAVING COUNT(*) > 1. If uniqueness holds only within a tenant or snapshot, group by the full composite key, such as tenant ID plus order ID.

Can dbt tests replace dashboard QA?

No. dbt's generic tests (unique, not_null, accepted_values, relationships) and singular SQL tests check tables and models. They don't exercise the joins, aggregations and filter states a dashboard applies. A model can pass every dbt test while a card still shows the wrong number. Run both.

Why doesn't SUM(DISTINCT) fix a fan-out total?

SUM(DISTINCT) removes repeated values, not repeated orders. Two different orders worth $100 each collapse into a single $100, so the total comes out too low instead of too high. In this article's example it returns $150 instead of $250. Fix the grain instead: aggregate item rows to one row per order before joining.

How often should you rerun data quality checks?

Rerun the affected checks after any change to extraction, transformations, metric definitions, joins, filters or reporting configuration, and before each release. Stable rules like uniqueness and not-null belong in automated tests that run with every load; filter and delivery checks run whenever the dashboard changes.

---

See Databrain on your own data

30 minutes. We connect your warehouse and show you governed answers for your team, or analytics inside your product, on your own schema.

Subscribe to our newsletter

Product updates and practical guides. No spam, unsubscribe anytime.

You're in. A welcome email is on its way.

That did not go through. Check the email and try again.