---
title: Choose a dataset mode
description: A decision flow for picking cube, lattice, hybrid or rows on data Dashies provides, and how to tell when the server picked one you did not expect.
updated: 2026-09-20
tier: free
---

On the data Dashies provides, each dataset in a dashboard picks one of four
materializations independently, and the choice is mechanical once you know two
things about the dataset: whether any measure is non-additive, and how its
filters work. The warning headed **This flow is about data Dashies provides**
says what that phrase rules out and why.

This page is the decision flow. For what each mode actually is and why the
positioning runs the way it does, see
[Datasets and the four modes](/concepts/dataset-modes#the-four-modes).

:::warning{title="This flow is about data Dashies provides"}
**A dataset that reads a warehouse you connected, or the workspace's
uploaded-file source, does not choose between these four.** Its rows stay with
Dashies and are queried when a reader opens the page, so the only mode it can be
given is `resolved`, and a publish that asks for `cube`, `lattice`, `hybrid` or
`rows` on such a connection is refused. There is nothing here for you to decide.
The measures do not pick a different mode there either, but they can stop you
reaching it: `percentile_disc`, `stddev`, `variance` and `mode` cannot be worked
out on such a dataset, so declaring one is refused at publish, naming the
measures, rather than steered onto anything else
([the aggregate table](/reference/spec#measures)).

One exception to all of this, and it turns on the plan rather than on the
connection: on a **free** plan a dashboard built on the shared Dashies sample
data is prepared the in-file way instead, so this flow is the one that applies to
it.

**The connection also decides what the statement itself returns**, and that is
the part that goes wrong silently: a dataset whose rows Dashies holds returns one
row per underlying record and declares each number rather than computing it, so a
statement that has already aggregated gets aggregated a second time.
[Datasets](/reference/spec#datasets) carries that fork, and
[a warehouse dashboard is served](/guides/connect-warehouse#a-warehouse-dashboard-is-served)
carries the mechanism behind where those rows live.
:::

## The flow

Answer in order. The first rule that fires is your answer.

:::steps

### 1. Is every measure additive?

Additive means summing partial totals gives the right answer. `sum`, `count`,
`min`, and `max` are additive.

Non-additive means it is not. Any of these makes the dataset non-additive:

- `count(distinct ...)`
- `avg`
- `median`
- `percentile_cont` or any percentile
- `stddev`
- `variance`
- `mode(...)`

**If every measure is additive, use `cube`.** Stop here.

### 2. Is every dimension low-cardinality?

That means every field the dashboard filters, charts, or tabulates on has a small
bounded set of values, and dates are bucketed rather than raw timestamps.

**If not, use `rows`.** Skip to step 4.

### 3. Are all the filters single-select?

A single-select filter fixes one value at a time.

**If yes, use `lattice`.** Stop here.

**If a multi-select or range filter has to stay exact on a non-composable
measure, use `hybrid`.** Non-composable means a distinct count, a true average, a
median, a percentile, a standard deviation, a variance, or a mode. A sum, count,
min, max, or a ratio of them composes across selected values without help, so
those do not force `hybrid`.

### 4. If you landed on `rows`, decide where the bytes go

A `rows` dataset can declare Parquet offload, which moves its rows out of the
shared inline budget so it can grow between publishes.

**On a warehouse you connect this is not a declaration at all.** Its data already
lives in a Parquet object Dashies holds and answers from, which is the one shape
such a dashboard has, so there is nothing here to decide;
[offloading to Parquet](/concepts/dataset-modes#offloading-to-parquet) is what
that buys and what it costs.

Where it IS a declaration, three conditions hold, and each is a hard publish
error rather than a surprise:

- Only a `rows` dataset. Never `cube`, `lattice`, or `hybrid`.
- Only on a warehouse connection, never on `self`.
- A cap on how many of one dashboard's datasets may be backed that way. **The
  number is in [sizes and ceilings](/concepts/dataset-modes#sizes-and-ceilings)
  rather than here**, so there is one copy of it to keep right. Measured
  2026-09-20: the figure that stood on this page said two while that table said
  three.

**SQL Server had no Parquet path until 2026-09-11 and this page said so.** It has
one now, and a SQL Server dashboard is served like the other warehouse engines:
its data is held by Dashies rather than shipped inside the file. See
[Connect SQL Server](/guides/connect-warehouse/sqlserver), which carries what is
still narrower there - the statement shapes it refuses, the four column types it
cannot carry, and a case-insensitive collation that merges rows before Dashies
sees them.

:::

## The short form

| If | Use |
|---|---|
| Every measure is additive | `cube` |
| Something is non-additive, all dimensions low-cardinality, all filters single-select | `lattice` |
| As above, but a multi-select or range filter must stay exact on a non-composable measure | `hybrid` |
| A dimension cannot be bounded, or you need row-level detail | `rows` |

Cost and power both run `cube` then `lattice` then `hybrid` then `rows`. Take the
first one that is correct, not the most capable one. The reasoning behind that
ordering is in
[how the choice is actually made](/concepts/dataset-modes#how-the-choice-is-actually-made).

## You do not have to declare it

Leave `mode` off and the server auto-selects `cube` or `lattice` from the
measures and dimensions. `rows` and `hybrid` ship row-level bytes, so they must
be declared explicitly.

The publish report's `mode_choices` says what each dataset resolved to and why,
for example:

```bash
main -> lattice: a distinct count over low-cardinality dimensions
```

**Read it.** A mode you did not expect is usually the server noticing a measure
is not what you thought it was, which is worth knowing before it becomes a wrong
number.

## The rule is enforced, so a wrong choice is loud

A `cube` dataset whose SQL computes a non-additive aggregate is **rejected at
publish**, naming the construct. Why it refuses rather than warns is
[explained here](/concepts/measure-correctness#additive-and-non-additive). A `cube` measure declared with a non-additive
aggregation is a schema error. Validation warns about the same SQL earlier, when
your AI passes the dataset's mode.

:::warning{title="The enforcement reads the SQL text, so it has a blind spot"}
A ratio or a distinct count computed inside a CTE or a subquery, then selected as
a plain column, reads as additive from the outside and passes every check.

That is why [Verify your numbers](/guides/verify-your-numbers) is a required step
and not a nicety. The check there is exactly what catches a mode chosen wrongly
for this reason.
:::

## A lattice must stay bounded

A lattice precomputes one cell per filter state, so its size is roughly the
product of each dimension's number of values plus one. Every dimension declares
its bound: a list of values for a category, or buckets for a date.

If it grows past the inline cap, the fix is to drop or bound a dimension. **A
lattice can never offload to Parquet**, so there is no way to buy your way out of
an unbounded one. The arithmetic is in
[the lattice cell budget](/concepts/dataset-modes#the-lattice-cell-budget), and
[offloading to Parquet](/concepts/dataset-modes#offloading-to-parquet) covers what
Parquet does and does not buy.

## Check it worked

1. `mode_choices` in the publish report names the mode you intended for each
   dataset.
2. The publish was not refused for a non-additive aggregate in a `cube`.
3. You ran [Verify your numbers](/guides/verify-your-numbers) on each additive
   measure, and the two legs agreed. That is the step that confirms the mode was
   right, rather than merely accepted.
