Step-by-step playbook for diagnosing the grain mismatch that causes wrong numbers, then selecting and constructing the right LOD expression (FIXED, INCLUDE, EXCLUDE) or table calculation. Includes the LOD-vs-table-calc decision, worked examples, and the common double-counting fixes. Owned by tableau-viz-engineer.
Scanned 9/23/2026
npx -y skills add mcorbett51090/RavenClaude --skill lod-expression-builder --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Lod Expression Builder?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mcorbett51090-lod-expression-builder)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: lod-expression-builder
description: "Step-by-step playbook for diagnosing the grain mismatch that causes wrong numbers, then selecting and constructing the right LOD expression (FIXED, INCLUDE, EXCLUDE) or table calculation. Includes the LOD-vs-table-calc decision, worked examples, and the common double-counting fixes. Owned by tableau-viz-engineer."
---
# LOD Expression Builder
## When to invoke
- A calculated field is returning wrong totals, double-counted numbers, or unexpected NULLs.
- Deciding between `FIXED`, `INCLUDE`, `EXCLUDE`, and a table calculation.
- Building a ratio-of-total, customer-first-order date, or cohort-entry metric.
- Aggregation at a different granularity than the view's level of detail.
## Step 1 — State the grain before writing a single calc
Answer these before opening the calculation editor:
1. **What is the view grain?** (e.g., one row per order line, per customer per month)
2. **What grain does the metric need?** (e.g., total revenue per customer, regardless of product)
3. **Are they the same?** If yes, a regular aggregate (`SUM`, `AVG`) is sufficient. If no, an LOD is needed.
Most "wrong number" bugs are grain bugs, not calculation bugs. Naming the mismatch first saves an hour of trial and error.
## Step 2 — Choose the expression type
| You need | Use | Example |
|---|---|---|
| A result locked to a specific dimension, regardless of view filters or granularity | `FIXED` | `{FIXED [Customer ID]: SUM([Revenue])}` — customer lifetime revenue |
| A result computed at a *finer* grain than the view | `INCLUDE` | `{INCLUDE [Order ID]: SUM([Qty])}` — average items per order, view is by month |
| A result computed at a *coarser* grain than the view (exclude a dimension) | `EXCLUDE` | `{EXCLUDE [Region]: SUM([Revenue])}` — national total on a region-level view |
| A ranking, running total, moving average, or percent-of-total that depends on the *current view layout* | Table calculation | `RANK(SUM([Revenue]))` with correct addressing/partitioning |
**FIXED does NOT respect dimension filters** (only context filters, data-source filters, and extract filters override it). If the metric must respect a user's filter, use `INCLUDE` or a table calculation — or promote the filter to a context filter explicitly.
## Step 3 — Construct the FIXED expression
```
{ FIXED [Dim1], [Dim2] : AGG([Measure]) }
```
- List every dimension needed to define the grain of the result.
- `AGG` must be a row-level expression wrapped in `SUM`/`MIN`/`MAX`/`AVG`/`COUNTD`.
- To use the result in another aggregate in the view, wrap it: `SUM({ FIXED ... })`.
**Worked example — customer first order date:**
```
{ FIXED [Customer ID] : MIN([Order Date]) }
```
Place this in a calculated field `[Customer First Order Date]`. The view can then be at any grain (order-level, month-level) and the field always returns the per-customer minimum.
## Step 4 — Construct the INCLUDE / EXCLUDE expression
```
{ INCLUDE [Extra Dim] : AGG([Measure]) }
{ EXCLUDE [Dim To Drop] : AGG([Measure]) }
```
INCLUDE adds a dimension to the computation; EXCLUDE removes one. Both respect dimension filters (unlike FIXED).
**Worked example — average order size at a monthly view:**
```
{ INCLUDE [Order ID] : SUM([Revenue]) }
```
This computes per-order revenue at the order-ID grain, then the view aggregates those results at the month level (the outer `AVG` in the view field becomes the average per-order revenue by month).
## Step 5 — Table calculation addressing and partitioning
Table calculations run on the query result already in the view, not on the underlying data. The two settings that matter:
| Setting | Meaning |
|---|---|
| **Addressing** (Compute Using) | The dimensions the calculation moves *across* (e.g., month-over-month: address = Date) |
| **Partitioning** | The dimensions that *reset* the calculation (e.g., each Region resets the running total) |
Never leave addressing on "Automatic" for a published workbook — it depends on field order in the view, which changes when users rearrange. Specify addressing explicitly.
## Double-counting diagnosis checklist
- [ ] Is there a JOIN that fans out rows (many-to-many)? → switch to a relationship or pre-aggregate.
- [ ] Is `SUM` applied to a pre-aggregated field? → use `ATTR` or rewrite the source.
- [ ] Is a FIXED LOD returning a value per row that gets `SUM`med again? → wrap in `MAX` or `MIN` if it's a row-level attribute, not a sum.
- [ ] Is a blend pulling in multiple matching rows from the secondary source? → blend should join on all granularity keys; consider converting to a relationship.
## Pitfalls
- Putting a FIXED LOD inside a `SUM` in the view when the FIXED already returns a total — you get total × row count.
- Using EXCLUDE when FIXED would be clearer — EXCLUDE is elegant when the dimension set is large; otherwise state the grain explicitly.
- Setting "Compute Using: Table (across)" on a multi-partition layout — the table calculation resets in unexpected places.
- Forgetting that FIXED ignores dimension filters: a FIXED customer revenue will ignore a Region quick-filter unless you promote that filter to a context filter.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!