Tableau LOD Expressions Explained: Fixing Wrong Aggregation Levels
Your dashboard says you have 4,200 customers. Your analyst says it's 3,800. Both of you are looking at the same database, and both numbers are technically correct β they're just computed at different levels of detail. This is the aggregation problem that plagues Tableau users at every skill level, and it's exactly what Level of Detail (LOD) expressions were built to solve.
What you'll learn
- How Tableau's default aggregation works and where it breaks down
- The difference between
FIXED,INCLUDE, andEXCLUDELOD expressions - Practical examples of each type with real field names
- How LOD expressions interact with filters
- Common mistakes and how to avoid them
Prerequisites
You should have a working knowledge of Tableau Desktop and be comfortable creating calculated fields. These examples use a sample order dataset with fields like Customer ID, Order ID, Region, Category, and Sales. No specific version is required, though LOD expressions have been available since Tableau 9.0.
Why Default Aggregation Breaks Down
Tableau aggregates every measure to match the dimensions on your current view. Drop Sales into a chart with Region on the x-axis, and you get total sales per region. That's intuitive. The trouble starts when you want a measure that should be computed at a different granularity than the view you're building.
Say you want the average number of orders per customer, broken down by region. The naive approach β dragging COUNTD(Order ID) onto the view β gives you the count of distinct orders in each region, not the average per customer within that region. Tableau computes at the view level, not the customer level. That single distinction causes more confusing dashboards than almost anything else.
What LOD Expressions Actually Do
An LOD expression tells Tableau: compute this calculation at this specific level of detail, regardless of what dimensions are on the view. The result is a value attached to each row of the underlying data, and Tableau can then aggregate that pre-computed value however you need in the view.
The syntax looks like this:
{ FIXED [Dimension1], [Dimension2] : AGG([Measure]) }The curly braces are the signal that you're writing an LOD expression. The keyword before the colon (FIXED, INCLUDE, or EXCLUDE) controls how the expression relates to the current view's dimensions. The aggregation after the colon is what gets computed.
FIXED: Anchor to Specific Dimensions
FIXED is the workhorse. It computes the aggregation using exactly the dimensions you list, ignoring whatever is on the view β and, critically, ignoring most filters (more on that in the pitfalls section).
Use case: You want each customer's first order date, regardless of what's on the view.
{ FIXED [Customer ID] : MIN([Order Date]) }This produces one value per customer: the earliest order date for that customer. You can then use this field in a view that has Region, Category, or any other dimension on it, and the first-order date will remain correct for each customer β it won't re-aggregate to the view level.
Another common use case: Counting customers who meet a condition, such as customers who placed more than three orders.
{ FIXED [Customer ID] : COUNTD([Order ID]) }Name this Orders per Customer. Now you can drop it into a view and use COUNTD([Customer ID]) as your base, filtering rows where Orders per Customer > 3 without affecting the customer-level count itself.
INCLUDE: Add Dimensions to the View's Context
INCLUDE computes the aggregation at a more granular level than the view, by adding extra dimensions to the view's existing set. You use it when you want to compute something at a finer grain and then aggregate that result up.
Use case: Average sales per order, broken down by category.
If you just use AVG([Sales]) in a view with Category, Tableau averages across all rows in that category β one row per line item. You actually want the total sales per order, averaged across orders within the category. INCLUDE handles this:
{ INCLUDE [Order ID] : SUM([Sales]) }This computes the total sales for each order. When you drop this into a view with Category on the row shelf and wrap it in AVG(), Tableau computes the average order value within each category β which is the number you actually wanted.
EXCLUDE: Remove Dimensions From the View's Context
EXCLUDE is the inverse: it computes at a coarser level than the view by dropping specified dimensions out. This is useful for computing totals or reference values at a higher level while keeping the granular view intact.
Use case: Show each region's sales alongside the overall total, to compute a percentage-of-total column.
{ EXCLUDE [Region] : SUM([Sales]) }In a view broken down by Region, this expression ignores Region and returns the grand total sales for every row. You can then divide SUM([Sales]) by this EXCLUDE expression to get a clean percentage-of-total column that updates correctly when you filter other dimensions.
One important distinction: EXCLUDE only works relative to the view's dimensions. If the dimension you're trying to exclude isn't on the view, the expression behaves like FIXED with no dimensions specified β which returns a single grand total.
How LOD Expressions Interact With Filters
This is the part that trips people up most. Tableau's filter order of operations matters a lot here.
Context filters run before FIXED LOD expressions. If you promote a filter to a context filter, it will affect even FIXED calculations.
Regular dimension filters run after FIXED LOD expressions. This means a FIXED expression computes on the full dataset, and then your filter hides rows β it does not recompute the FIXED value. This is often exactly what you want (for example, counting total customers who ever bought in a category, regardless of which date range is currently filtered). But it can also silently produce wrong answers if you expect FIXED to respect your filters.
Quick rule of thumb:
- Want the LOD to respect a filter? Make it a context filter.
- Want the LOD to ignore a filter? Leave it as a regular dimension filter and use
FIXED. INCLUDEandEXCLUDEexpressions respect regular dimension filters, which makes them easier to reason about in filtered dashboards.
Practical Example: Cohort Customer Count
Here's a full worked example. You want to show how many customers acquired in each quarter are still placing orders today, broken down by product category.
Step 1: Compute the acquisition quarter for each customer.
{ FIXED [Customer ID] : MIN(DATETRUNC('quarter', [Order Date])) }Name this Acquisition Quarter. Because it's FIXED on Customer ID, it returns the same value for that customer regardless of what filters or dimensions are on the view.
Step 2: Build a view with Acquisition Quarter on rows and Category on columns. Drop COUNTD([Customer ID]) onto the text shelf. Tableau now counts distinct customers per acquisition quarter per category β correctly, because the acquisition quarter is stable at the customer level regardless of which orders are visible.
Without the LOD expression, placing a date filter on the view would skew the acquisition quarter toward whichever date range is selected, making early cohorts look smaller than they are.
Common Pitfalls
Using a dimension that doesn't exist in the data source
If your FIXED expression references a calculated dimension rather than a raw field, Tableau may evaluate the expression before the calculation is complete. Build your calculations in the right order, or use context filters to ensure dependencies are resolved first.
Aggregating an LOD expression with the wrong function
An LOD expression returns a value per row of the underlying data. When you drop it onto a view, Tableau still needs to aggregate it. Using SUM on a per-customer count can inflate numbers badly. Think about what aggregation makes sense: AVG, COUNTD, MAX, or MIN are usually more appropriate than SUM for LOD-computed values.
Confusing FIXED with INCLUDE on filtered dashboards
If your dashboard has active user filters and you use FIXED, the calculation ignores those filters (unless you use context filters). Dashboards that look fine in development can produce surprising numbers in production when end users start filtering. Test LOD expressions with filters active before shipping.
Deeply nested LOD expressions
Tableau technically allows nesting LOD expressions inside each other. In practice, anything more than one level of nesting becomes hard to debug and often signals that the underlying data model should be reshaped. If you find yourself writing a FIXED inside an INCLUDE, step back and consider whether a data source join or a custom SQL extract would be cleaner.
Wrapping Up
LOD expressions are one of the features that separate beginner Tableau work from polished, production-quality analytics. Once you internalize the three keywords and the filter order of operations, most aggregation mismatches become straightforward to diagnose and fix.
Here are your next steps:
- Open your most confusing existing dashboard and identify one metric that might be computing at the wrong level of detail. Rewrite it as a
FIXEDexpression and check whether the numbers change. - Practice the
INCLUDEpattern on an average-per-entity calculation β average order value or average sessions per user are good candidates. - Test every new LOD expression with at least one active filter to confirm it behaves as expected in a filtered context.
- Review Tableau's filter order of operations diagram until context filters versus regular filters feel intuitive β that single concept prevents the majority of LOD bugs.
- If a calculation is getting unwieldy, consider moving the logic into a custom SQL layer or a data source extract so the Tableau workbook stays readable.
π€ Share this article
Sign in to saveRelated Articles
Comments (0)
No comments yet. Be the first!