---
title: Choose a dataset mode
description: A decision flow for picking cube, lattice, hybrid, or rows per dataset, plus how to tell when the server picked one you did not expect.
updated: 2026-08-04
tier: free
---

Each dataset in a dashboard picks one of four materializations independently. The
choice is mechanical once you know two things about the dataset: whether any
measure is non-additive, and how its filters work.

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).

## 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 on a warehouse connection can declare Parquet offload, which
moves its rows out of the shared inline budget so it can grow between publishes.

Three conditions, 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`.
- At most two per dashboard.

**SQL Server has no Parquet path**, and no `rows` or `hybrid` mode at all. See
[Connect SQL Server](/guides/connect-warehouse/sqlserver).

:::

## 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.
