Verify your numbers
Cross-check every published measure against an independent aggregate. A dashboard that validates and publishes cleanly can still ship a wrong number.
Run this before you share a dashboard link with anyone. It takes two queries per measure.
validate_cube_sql proves the SQL runs, not that it is correct
Dashies checks a great deal at publish time, and none of it is a correctness proof. Two failures pass every automatic check and then refresh to a plausible wrong number forever:
- A non-additive measure hidden in a CTE or subquery. A ratio or a
count(distinct ...)computed inside a derived table and then selected as a plain column reads as additive from the outside. - A fan-out join. Joining to a one-to-many table multiplies every summed measure on the parent row.
You wrote the SQL, so you have the context to catch both. Nothing downstream will. For why no automatic check can close this gap, see what no check can do for you.
The check
For every measure declared as additive, compute its total two ways and compare.
Leg A: total the cube. Wrap the cube SQL and sum the measure out of it, so the answer comes back as one row.
select sum(revenue) as total
from (
select date_trunc('month', o.ordered_at)::date as month,
c.region as region,
sum(o.amount) as revenue,
count(*) as orders
from orders o
join order_items i on i.order_id = o.id
join customers c on c.id = o.customer_id
where o.ordered_at >= now() - interval '12 months'
group by 1, 2
) c
Leg B: total the base table, independently. Aggregate the single un-joined source a different way.
select sum(amount) as total
from orders
where ordered_at >= now() - interval '12 months'
Run both through your AI's validate_cube_sql tool against the same data source.
Neither needs the row echo, since both return one row.
The two numbers must match.
In the example above they do not. order_items is one-to-many against orders,
so the join multiplies each order's amount by its item count, and leg A comes
back several times leg B.
Leg B must not repeat the cube's joins
The whole point of leg B is that it is independent. Aggregating over the cube's
own FROM clause reproduces exactly the double-count you are checking for, and
the two legs then agree while both being wrong.
Aggregate the base table on its own. Keep the same WHERE clause, so both legs
cover the same rows, and change nothing else.
Reading the result
| What you see | What it means | What to do |
|---|---|---|
| The two totals match | The re-summing a cube does is sound for this measure. Repeat on one or two filter slices for stronger evidence. | Nothing. |
| Leg A is a clean multiple of leg B | A join is fanning rows out. | Pre-aggregate the fanning side to the grain first, or dedupe, so each base row counts once. |
| Leg A differs in some other way | The measure is not additive. There is a ratio, distinct count, or average hidden inside it. | Declare it as a ratio measure with a numerator and denominator, or move the dataset to a mode that recomputes it exactly. See Choose a dataset mode. |
When the publish report tells you to run this
The dry-run report carries an obligations entry whenever a cube is built over
more than one row source: a join, a CTE, a comma join, or a derived table.
That is the format prompting you to run the check above, because no static
analysis can judge it.
An obligation is a prompt, never a substitute. And read the converse carefully:
Empty obligations does not mean your numbers are right
An empty obligations list means only that the cube reads one row source, so it
cannot fan out. A non-additive aggregate or a mis-declared measure on a
single-source cube is still entirely yours to catch.
Non-additive measures need this more, not less
A median, a percentile, a distinct count, or a true average is not re-derivable from anything downstream. See additive and non-additive for which aggregates fall on which side, and flow and stock for the measure shape that produced the worst incident this product has had. Nobody will ever notice a wrong one, so prove it before you publish.
Compute the same value a second, independent way and compare. A rank-based query
using row_number() works on every engine:
select max(x) as median_x
from (
select x, row_number() over (order by x) as rn, count(*) over () as n
from measurements
where x is not null
) t
where rn <= (n + 1) / 2
Compare like with like, or this check will flag a correct cube
That query returns the discrete lower median: on an even-sized population it
gives you the lower of the two middle values. It agrees exactly with
percentile_disc(0.5).
It does not agree with percentile_cont(0.5), which interpolates and returns
the average of those two values. So if your cube uses percentile_cont and the
column has an even number of non-null rows, the two legs differ by design and
the cube is fine.
Compare percentile_disc against the query above, or percentile_cont against
an interpolating second leg. A verification procedure that reports false
positives is worse than none, because the next real discrepancy gets waved off as
noise.
Two engine-specific traps are worth knowing before you run it:
- On BigQuery there is no aggregate percentile function, and the obvious substitute is wrong twice over. See the BigQuery page.
- A
count(*)where you meantcount(x)skews a median low whenever the column has nulls, and agrees exactly when it does not. On a column with 8,917 nulls in 296,104 rows, that mistake returned 21,409 against a true median of 22,767, about 6 percent low, and it validated and published clean. This is precisely the shape that survives casual testing and reaches production.
Which median form to use, and which are verified
| Engine | Exact median | Verified against a live connection |
|---|---|---|
| PostgreSQL | percentile_cont(0.5) within group (order by x) | yes |
| BigQuery | array_agg(x ignore nulls order by x)[safe_offset(div(count(x), 2))] | yes |
| Snowflake | percentile_cont(0.5) within group (order by x) | no |
| Amazon Redshift | percentile_cont(0.5) within group (order by x) | no |
| Databricks | percentile(x, 0.5) | no |
| SQL Server | no aggregate form confirmed | no |
The four marked no are the documented syntax rather than something we have run. On those, prove the median against the independent query above before you publish, rather than trusting the syntax.
Check it worked
You are done when, for each measure a tile actually displays:
- Leg A and leg B agree, ungrouped.
- They agree again over at least one filter slice, for example a single region or a single month.
- Any measure that is a median, percentile, distinct count, or average has been confirmed against an independent computation.
Then open the published dashboard and confirm the headline figure on the page matches leg B. If it does not, the number the dashboard displays is not the number you just proved, and the difference is a binding or a scope problem rather than a SQL one.
Do this again after any SQL change
The result is a claim about the SQL you ran it on. A later edit that adds a join or moves an aggregate into a CTE re-opens both failure modes, and the next scheduled refresh will publish the new number without asking anyone.